diff --git a/src/NoteData.cpp b/src/NoteData.cpp index 24bb051fea..fbbd15fb94 100644 --- a/src/NoteData.cpp +++ b/src/NoteData.cpp @@ -1,1343 +1,1343 @@ -/* - * NoteData is organized by: - * track - corresponds to different columns of notes on the screen - * row/index - corresponds to subdivisions of beats - */ - -#include "global.h" -#include "NoteData.h" -#include "RageUtil.h" -#include "RageLog.h" -#include "XmlFile.h" -#include "GameState.h" // blame radar calculations. -#include "Foreach.h" -#include "RageUtil_AutoPtr.h" - -REGISTER_CLASS_TRAITS( NoteData, new NoteData(*pCopy) ) - -void NoteData::Init() -{ - m_TapNotes = vector(); // ensure that the memory is freed -} - -void NoteData::SetNumTracks( int iNewNumTracks ) -{ - ASSERT( iNewNumTracks > 0 ); - - m_TapNotes.resize( iNewNumTracks ); -} - -bool NoteData::IsComposite() const -{ - for( int track = 0; track < GetNumTracks(); ++track ) - { - FOREACHM_CONST( int, TapNote, m_TapNotes[track], tn ) - if( tn->second.pn != PLAYER_INVALID ) - return true; - } - return false; -} - -// Clear (rowBegin,rowEnd). -void NoteData::ClearRangeForTrack( int rowBegin, int rowEnd, int iTrack ) -{ - // Optimization: if the range encloses everything, just clear the whole maps. - if( rowBegin == 0 && rowEnd == MAX_NOTE_ROW ) - { - m_TapNotes[iTrack].clear(); - return; - } - - /* If the range is empty, don't do anything. Otherwise, an empty range will - * cause hold notes to be split when they shouldn't be. */ - if( rowBegin == rowEnd ) - return; - - NoteData::TrackMap::iterator lBegin, lEnd; - GetTapNoteRangeInclusive( iTrack, rowBegin, rowEnd, lBegin, lEnd ); - - if( lBegin != lEnd && lBegin->first < rowBegin && lBegin->first + lBegin->second.iDuration > rowEnd ) - { - /* A hold note overlaps the whole range. Truncate it, and add the - * remainder to the end. */ - TapNote tn1 = lBegin->second; - TapNote tn2 = tn1; - - int iEndRow = lBegin->first + tn1.iDuration; - int iRow = lBegin->first; - - tn1.iDuration = rowBegin - iRow; - tn2.iDuration = iEndRow - rowEnd; - - SetTapNote( iTrack, iRow, tn1 ); - SetTapNote( iTrack, rowEnd, tn2 ); - - // We may have invalidated our iterators. - GetTapNoteRangeInclusive( iTrack, rowBegin, rowEnd, lBegin, lEnd ); - } - else if( lBegin != lEnd && lBegin->first < rowBegin ) - { - // A hold note overlaps the beginning of the range. Truncate it. - TapNote &tn1 = lBegin->second; - int iRow = lBegin->first; - tn1.iDuration = rowBegin - iRow; - - ++lBegin; - } - - if( lBegin != lEnd ) - { - NoteData::TrackMap::iterator prev = lEnd; - --prev; - TapNote tn = lBegin->second; - int iRow = prev->first; - if( tn.type == TapNote::hold_head && iRow + tn.iDuration > rowEnd ) - { - // A hold note overlaps the end of the range. Separate it. - SetTapNote( iTrack, iRow, TAP_EMPTY ); - - int iAdd = rowEnd - iRow; - tn.iDuration -= iAdd; - iRow += iAdd; - SetTapNote( iTrack, iRow, tn ); - lEnd = prev; - } - - // We may have invalidated our iterators. - GetTapNoteRangeInclusive( iTrack, rowBegin, rowEnd, lBegin, lEnd ); - } - - m_TapNotes[iTrack].erase( lBegin, lEnd ); -} - -void NoteData::ClearRange( int rowBegin, int rowEnd ) -{ - for( int t=0; t < GetNumTracks(); ++t ) - ClearRangeForTrack( rowBegin, rowEnd, t ); -} - -void NoteData::ClearAll() -{ - for( int t=0; t rowFromEnd ) - return; // empty range - - const int rowToEnd = (rowFromEnd-rowFromBegin) + rowToBegin; - const int iMoveBy = rowToBegin-rowFromBegin; - - // Clear the region. - ClearRange( rowToBegin, rowToEnd ); - - for( int t=0; tsecond; - if( head.type == TapNote::empty ) - continue; - - if( head.type == TapNote::hold_head ) - { - int iStartRow = lBegin->first + iMoveBy; - int iEndRow = iStartRow + head.iDuration; - - iStartRow = clamp( iStartRow, rowToBegin, rowToEnd ); - iEndRow = clamp( iEndRow, rowToBegin, rowToEnd ); - - this->AddHoldNote( t, iStartRow, iEndRow, head ); - } - else - { - int iTo = lBegin->first + iMoveBy; - if( iTo >= rowToBegin && iTo <= rowToEnd ) - this->SetTapNote( t, iTo, head ); - } - } - } -} - -void NoteData::CopyAll( const NoteData& from ) -{ - *this = from; -} - -bool NoteData::IsRowEmpty( int row ) const -{ - for( int t=0; t& addTo ) const -{ - for( int t=0; t=0; t-- ) - { - if( GetTapNote( t, row ).type == TapNote::empty ) - { - iEmptyTrackOut = t; - return true; - } - } - return false; -} - -int NoteData::GetNumTracksWithTap( int row ) const -{ - int iNum = 0; - for( int t=0; t=0; t-- ) - { - const TapNote &tn = GetTapNote( t, row ); - if( tn.type == TapNote::tap || tn.type == TapNote::lift || tn.type == TapNote::hold_head ) - return t; - } - return -1; -} - -void NoteData::AddHoldNote( int iTrack, int iStartRow, int iEndRow, TapNote tn ) -{ - ASSERT( iStartRow>=0 && iEndRow>=0 ); - ASSERT_M( iEndRow >= iStartRow, ssprintf("EndRow %d < StartRow %d",iEndRow,iStartRow) ); - - /* Include adjacent (non-overlapping) hold notes, since we need to merge with them. */ - NoteData::TrackMap::iterator lBegin, lEnd; - GetTapNoteRangeInclusive( iTrack, iStartRow, iEndRow, lBegin, lEnd, true ); - - // Look for other hold notes that overlap and merge them into add. - for( iterator it = lBegin; it != lEnd; ++it ) - { - int iOtherRow = it->first; - const TapNote &tnOther = it->second; - if( tnOther.type == TapNote::hold_head ) - { - iStartRow = min( iStartRow, iOtherRow ); - iEndRow = max( iEndRow, iOtherRow + tnOther.iDuration ); - } - } - - tn.iDuration = iEndRow - iStartRow; - - // Remove everything in the range. - while( lBegin != lEnd ) - { - iterator next = lBegin; - ++next; - - RemoveTapNote( iTrack, lBegin ); - - lBegin = next; - } - - /* Additionally, if there's a tap note lying at the end of our range, - * remove it too. */ - SetTapNote( iTrack, iEndRow, TAP_EMPTY ); - - // add a tap note at the start of this hold - SetTapNote( iTrack, iStartRow, tn ); -} - -/* Determine if a hold note lies on the given spot. Return true if so. If - * pHeadRow is non-NULL, return the row of the head. */ -bool NoteData::IsHoldHeadOrBodyAtRow( int iTrack, int iRow, int *pHeadRow ) const -{ - const TapNote &tn = GetTapNote( iTrack, iRow ); - if( tn.type == TapNote::hold_head ) - { - if( pHeadRow != NULL ) - *pHeadRow = iRow; - return true; - } - - return IsHoldNoteAtRow( iTrack, iRow, pHeadRow ); -} - -/* Determine if a hold note lies on the given spot. Return true if so. If - * pHeadRow is non-NULL, return the row of the head. (Note that this returns - * false if a hold head lies on iRow itself.) */ -/* XXX: rename this to IsHoldBodyAtRow */ -bool NoteData::IsHoldNoteAtRow( int iTrack, int iRow, int *pHeadRow ) const -{ - int iDummy; - if( pHeadRow == NULL ) - pHeadRow = &iDummy; - - /* Starting at iRow, search upwards. If we find a TapNote::hold_head, we're within - * a hold. If we find a tap, mine or attack, we're not--those never lie - * within hold notes. Ignore autoKeysound. */ - FOREACH_NONEMPTY_ROW_IN_TRACK_RANGE_REVERSE( *this, iTrack, r, 0, iRow ) - { - const TapNote &tn = GetTapNote( iTrack, r ); - switch( tn.type ) - { - case TapNote::hold_head: - if( tn.iDuration + r < iRow ) - return false; - *pHeadRow = r; - return true; - - case TapNote::tap: - case TapNote::mine: - case TapNote::attack: - case TapNote::lift: - case TapNote::fake: - return false; - - case TapNote::empty: - case TapNote::autoKeysound: - // ignore - continue; - DEFAULT_FAIL( tn.type ); - } - } - - return false; -} - -bool NoteData::IsEmpty() const -{ - for( int t=0; t < GetNumTracks(); t++ ) - { - int iRow = -1; - if( !GetNextTapNoteRowForTrack( t, iRow ) ) - continue; - - return false; - } - - return true; -} - -int NoteData::GetFirstRow() const -{ - int iEarliestRowFoundSoFar = -1; - - for( int t=0; t < GetNumTracks(); t++ ) - { - int iRow = -1; - if( !GetNextTapNoteRowForTrack( t, iRow ) ) - continue; - - if( iEarliestRowFoundSoFar == -1 ) - iEarliestRowFoundSoFar = iRow; - else - iEarliestRowFoundSoFar = min( iEarliestRowFoundSoFar, iRow ); - } - - if( iEarliestRowFoundSoFar == -1 ) // there are no notes - return 0; - - return iEarliestRowFoundSoFar; -} - -int NoteData::GetLastRow() const -{ - int iOldestRowFoundSoFar = 0; - - for( int t=0; t < GetNumTracks(); t++ ) - { - int iRow = MAX_NOTE_ROW; - if( !GetPrevTapNoteRowForTrack( t, iRow ) ) - continue; - - /* XXX: We might have a hold note near the end with autoplay sounds - * after it. Do something else with autoplay sounds ... */ - const TapNote &tn = GetTapNote( t, iRow ); - if( tn.type == TapNote::hold_head ) - iRow += tn.iDuration; - - iOldestRowFoundSoFar = max( iOldestRowFoundSoFar, iRow ); - } - - return iOldestRowFoundSoFar; -} - -bool NoteData::IsTap(const TapNote &tn, const int row) const -{ - return (tn.type != TapNote::empty && tn.type != TapNote::mine - && tn.type != TapNote::lift && tn.type != TapNote::fake - && tn.type != TapNote::autoKeysound - && GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(row)); -} - -bool NoteData::IsMine(const TapNote &tn, const int row) const -{ - return (tn.type == TapNote::mine - && GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(row)); -} - -bool NoteData::IsLift(const TapNote &tn, const int row) const -{ - return (tn.type == TapNote::lift - && GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(row)); -} - -bool NoteData::IsFake(const TapNote &tn, const int row) const -{ - return (tn.type == TapNote::fake - || !GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(row)); -} - -int NoteData::GetNumTapNotes( int iStartIndex, int iEndIndex ) const -{ - int iNumNotes = 0; - for( int t=0; tIsTap(GetTapNote(t, r), r)) - iNumNotes++; - } - } - - return iNumNotes; -} - -int NoteData::GetNumTapNotesInRow( int iRow ) const -{ - int iNumNotes = 0; - for( int t=0; tIsTap(GetTapNote(t, iRow), iRow)) - iNumNotes++; - } - return iNumNotes; -} - -int NoteData::GetNumRowsWithTap( int iStartIndex, int iEndIndex ) const -{ - int iNumNotes = 0; - FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( *this, r, iStartIndex, iEndIndex ) - if( IsThereATapAtRow(r) && GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(r) ) - iNumNotes++; - - return iNumNotes; -} - -int NoteData::GetNumMines( int iStartIndex, int iEndIndex ) const -{ - int iNumMines = 0; - - for( int t=0; tIsMine(GetTapNote(t, r), r)) - iNumMines++; - } - - return iNumMines; -} - -int NoteData::GetNumRowsWithTapOrHoldHead( int iStartIndex, int iEndIndex ) const -{ - int iNumNotes = 0; - FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( *this, r, iStartIndex, iEndIndex ) - if( IsThereATapOrHoldHeadAtRow(r) && GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(r) ) - iNumNotes++; - - return iNumNotes; -} - -bool NoteData::RowNeedsAtLeastSimultaneousPresses( int iMinSimultaneousPresses, const int row ) const -{ - int iNumNotesThisIndex = 0; - for( int t=0; t= iMinSimultaneousPresses; -} - -int NoteData::GetNumRowsWithSimultaneousPresses( int iMinSimultaneousPresses, int iStartIndex, int iEndIndex ) const -{ - /* Count the number of times you have to use your hands. This includes - * three taps at the same time, a tap while two hold notes are being held, - * etc. Only count rows that have at least one tap note (hold heads count). - * Otherwise, every row of hold notes counts, so three simultaneous hold - * notes will count as hundreds of "hands". */ - int iNum = 0; - FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( *this, r, iStartIndex, iEndIndex ) - { - if( !RowNeedsAtLeastSimultaneousPresses(iMinSimultaneousPresses,r) ) - continue; - if (!GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(r)) - continue; - iNum++; - } - - return iNum; -} - -int NoteData::GetNumRowsWithSimultaneousTaps( int iMinTaps, int iStartIndex, int iEndIndex ) const -{ - int iNum = 0; - FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( *this, r, iStartIndex, iEndIndex ) - { - if (!GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(r)) - continue; - int iNumNotesThisIndex = 0; - for( int t=0; t= iMinTaps ) - iNum++; - } - - return iNum; -} - -int NoteData::GetNumHoldNotes( int iStartIndex, int iEndIndex ) const -{ - int iNumHolds = 0; - for( int t=0; tsecond.type != TapNote::hold_head || - lBegin->second.subType != TapNote::hold_head_hold ) - continue; - if (!GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(lBegin->first)) - continue; - iNumHolds++; - } - } - return iNumHolds; -} - -int NoteData::GetNumRolls( int iStartIndex, int iEndIndex ) const -{ - int iNumRolls = 0; - for( int t=0; tsecond.type != TapNote::hold_head || - lBegin->second.subType != TapNote::hold_head_roll ) - continue; - if (!GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(lBegin->first)) - continue; - iNumRolls++; - } - } - return iNumRolls; -} - -int NoteData::GetNumLifts( int iStartIndex, int iEndIndex ) const -{ - int iNumLifts = 0; - - for( int t=0; tIsLift(GetTapNote(t, r), r)) - iNumLifts++; - } - - return iNumLifts; -} - -int NoteData::GetNumFakes( int iStartIndex, int iEndIndex ) const -{ - int iNumFakes = 0; - - for( int t=0; tIsFake(GetTapNote(t, r), r)) - iNumFakes++; - } - - return iNumFakes; -} - -bool NoteData::IsPlayer1(const int track, const TapNote &tn) const -{ - if (this->IsComposite()) - { - return tn.pn == PLAYER_1; - } - return track < (this->GetNumTracks() / 2); -} - -pair NoteData::GetNumTapNotesTwoPlayer( int iStartIndex, int iEndIndex ) const -{ - pair num(0, 0); - for( int t=0; tIsTap(tn, r)) - { - if (this->IsPlayer1(t, tn)) - num.first++; - else - num.second++; - } - } - } - return num; -} - -pair NoteData::GetNumRowsWithSimultaneousTapsTwoPlayer(int minTaps, - int startRow, - int endRow) const -{ - pair num(0, 0); - FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( *this, r, startRow, endRow ) - { - pair found(0, 0); - for( int t=0; tIsTap(tn, r)) - { - if (this->IsPlayer1(t, tn)) - found.first++; - else - found.second++; - } - } - if (found.first >= minTaps) - num.first++; - if (found.second >= minTaps) - num.second++; - } - return num; -} - -pair NoteData::GetNumJumpsTwoPlayer( int iStartIndex, int iEndIndex ) const -{ - return GetNumRowsWithSimultaneousTapsTwoPlayer( 2, iStartIndex, iEndIndex ); -} - -pair NoteData::GetNumHandsTwoPlayer( int iStartIndex, int iEndIndex ) const -{ - return GetNumRowsWithSimultaneousTapsTwoPlayer( 3, iStartIndex, iEndIndex ); -} - -pair NoteData::GetNumQuadsTwoPlayer( int iStartIndex, int iEndIndex ) const -{ - return GetNumRowsWithSimultaneousTapsTwoPlayer( 4, iStartIndex, iEndIndex ); -} - -pair NoteData::GetNumHoldNotesTwoPlayer( int iStartIndex, int iEndIndex ) const -{ - pair num(0, 0); - for( int t=0; tsecond.type != TapNote::hold_head || - lBegin->second.subType != TapNote::hold_head_hold ) - continue; - if (!GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(lBegin->first)) - continue; - if (this->IsPlayer1(t, lBegin->second)) - num.first++; - else - num.second++; - } - } - return num; -} - -pair NoteData::GetNumMinesTwoPlayer( int iStartIndex, int iEndIndex ) const -{ - pair num(0, 0); - for( int t=0; tIsMine(tn, r)) - { - if (this->IsPlayer1(t, tn)) - num.first++; - else - num.second++; - } - } - } - return num; -} - -pair NoteData::GetNumRollsTwoPlayer( int iStartIndex, int iEndIndex ) const -{ - pair num(0, 0); - for( int t=0; tsecond.type != TapNote::hold_head || - lBegin->second.subType != TapNote::hold_head_roll ) - continue; - if (!GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(lBegin->first)) - continue; - if (this->IsPlayer1(t, lBegin->second)) - num.first++; - else - num.second++; - } - } - return num; -} - -pair NoteData::GetNumLiftsTwoPlayer( int iStartIndex, int iEndIndex ) const -{ - pair num(0, 0); - for( int t=0; tIsLift(tn, r)) - { - if (this->IsPlayer1(t, tn)) - num.first++; - else - num.second++; - } - } - } - return num; -} - -pair NoteData::GetNumFakesTwoPlayer( int iStartIndex, int iEndIndex ) const -{ - pair num(0, 0); - for( int t=0; tIsFake(tn, r)) - { - if (this->IsPlayer1(t, tn)) - num.first++; - else - num.second++; - } - } - } - return num; -} - -/* -int NoteData::GetNumMinefields( int iStartIndex, int iEndIndex ) const -{ - int iNumMinefields = 0; - for( int t=0; tsecond.type != TapNote::hold_head || - begin->second.subType != TapNote::hold_head_mine ) - continue; - iNumMinefields++; - } - } - return iNumMinefields; -} -*/ - -// -1 for iOriginalTracksToTakeFrom means no track -void NoteData::LoadTransformed( const NoteData& in, int iNewNumTracks, const int iOriginalTrackToTakeFrom[] ) -{ - // reset all notes - Init(); - - SetNumTracks( iNewNumTracks ); - - // copy tracks - for( int t=0; t= %i (#tracks) (taking from %i)", - iOriginalTrack, in.GetNumTracks(), iOriginalTrackToTakeFrom[t])); - - if( iOriginalTrack == -1 ) - continue; - m_TapNotes[t] = in.m_TapNotes[iOriginalTrack]; - } -} - -void NoteData::MoveTapNoteTrack( int dest, int src ) -{ - if(dest == src) return; - m_TapNotes[dest] = m_TapNotes[src]; - m_TapNotes[src].clear(); -} - -void NoteData::SetTapNote( int track, int row, const TapNote& t ) -{ - DEBUG_ASSERT( track>=0 && track& addTo ) -{ - for( int t=0; t viTracks; - viTracks.clear(); - GetTracksHeldAtRow( row, viTracks ); - return viTracks.size(); -} - -bool NoteData::GetNextTapNoteRowForTrack( int track, int &rowInOut ) const -{ - const TrackMap &mapTrack = m_TapNotes[track]; - - // lower_bound and upper_bound have the same effect here because duplicate - // keys aren't allowed. - - // lower_bound "finds the first element whose key is not less than k" (>=); - // upper_bound "finds the first element whose key greater than k". They don't - // have the same effect, but lower_bound(row+1) should equal upper_bound(row). -glenn - TrackMap::const_iterator iter = mapTrack.lower_bound( rowInOut+1 ); // "find the first note for which row+1 < key == false" - if( iter == mapTrack.end() ) - return false; - - ASSERT( iter->first > rowInOut ); - rowInOut = iter->first; - return true; -} - -bool NoteData::GetPrevTapNoteRowForTrack( int track, int &rowInOut ) const -{ - const TrackMap &mapTrack = m_TapNotes[track]; - - // Find the first note >= rowInOut. - TrackMap::const_iterator iter = mapTrack.lower_bound( rowInOut ); - - // If we're at the beginning, we can't move back any more. - if( iter == mapTrack.begin() ) - return false; - - // Move back by one. - --iter; - ASSERT( iter->first < rowInOut ); - rowInOut = iter->first; - return true; -} - -void NoteData::GetTapNoteRange( int iTrack, int iStartRow, int iEndRow, TrackMap::iterator &lBegin, TrackMap::iterator &lEnd ) -{ - ASSERT_M( iTrack < GetNumTracks(), ssprintf("%i,%i", iTrack, GetNumTracks()) ); - TrackMap &mapTrack = m_TapNotes[iTrack]; - - if( iStartRow > iEndRow ) - { - lBegin = lEnd = mapTrack.end(); - return; - } - - if( iStartRow <= 0 ) - lBegin = mapTrack.begin(); // optimization - else if( iStartRow >= MAX_NOTE_ROW ) - lBegin = mapTrack.end(); // optimization - else - lBegin = mapTrack.lower_bound( iStartRow ); - - if( iEndRow <= 0 ) - lEnd = mapTrack.begin(); // optimization - else if( iEndRow >= MAX_NOTE_ROW ) - lEnd = mapTrack.end(); // optimization - else - lEnd = mapTrack.lower_bound( iEndRow ); -} - - -/* Include hold notes that overlap the edges. If a hold note completely surrounds the given - * range, included it, too. If bIncludeAdjacent is true, also include hold notes adjacent to, - * but not overlapping, the edge. */ -void NoteData::GetTapNoteRangeInclusive( int iTrack, int iStartRow, int iEndRow, TrackMap::iterator &lBegin, TrackMap::iterator &lEnd, bool bIncludeAdjacent ) -{ - GetTapNoteRange( iTrack, iStartRow, iEndRow, lBegin, lEnd ); - - if( lBegin != this->begin(iTrack) ) - { - iterator prev = Decrement(lBegin); - - const TapNote &tn = prev->second; - if( tn.type == TapNote::hold_head ) - { - int iHoldStartRow = prev->first; - int iHoldEndRow = iHoldStartRow + tn.iDuration; - if( bIncludeAdjacent ) - ++iHoldEndRow; - if( iHoldEndRow > iStartRow ) - { - // The previous note is a hold. - lBegin = prev; - } - } - } - - if( bIncludeAdjacent && lEnd != this->end(iTrack) ) - { - // Include the next note if it's a hold and starts on iEndRow. - const TapNote &tn = lEnd->second; - int iHoldStartRow = lEnd->first; - if( tn.type == TapNote::hold_head && iHoldStartRow == iEndRow ) - ++lEnd; - } -} - -void NoteData::GetTapNoteRangeExclusive( int iTrack, int iStartRow, int iEndRow, TrackMap::iterator &lBegin, TrackMap::iterator &lEnd ) -{ - GetTapNoteRange( iTrack, iStartRow, iEndRow, lBegin, lEnd ); - - // If end-1 is a hold_head, and extends beyond iEndRow, exclude it. - if( lBegin != lEnd && lEnd != this->begin(iTrack) ) - { - iterator prev = lEnd; - --prev; - if( prev->second.type == TapNote::hold_head ) - { - int localStartRow = prev->first; - const TapNote &tn = prev->second; - if( localStartRow + tn.iDuration >= iEndRow ) - lEnd = prev; - } - } -} - -void NoteData::GetTapNoteRange( int iTrack, int iStartRow, int iEndRow, TrackMap::const_iterator &lBegin, TrackMap::const_iterator &lEnd ) const -{ - TrackMap::iterator const_begin, const_end; - const_cast(this)->GetTapNoteRange( iTrack, iStartRow, iEndRow, const_begin, const_end ); - lBegin = const_begin; - lEnd = const_end; -} - -void NoteData::GetTapNoteRangeInclusive( int iTrack, int iStartRow, int iEndRow, TrackMap::const_iterator &lBegin, TrackMap::const_iterator &lEnd, bool bIncludeAdjacent ) const -{ - TrackMap::iterator const_begin, const_end; - const_cast(this)->GetTapNoteRangeInclusive( iTrack, iStartRow, iEndRow, const_begin, const_end, bIncludeAdjacent ); - lBegin = const_begin; - lEnd = const_end; -} - -void NoteData::GetTapNoteRangeExclusive( int iTrack, int iStartRow, int iEndRow, TrackMap::const_iterator &lBegin, TrackMap::const_iterator &lEnd ) const -{ - TrackMap::iterator const_begin, const_end; - const_cast(this)->GetTapNoteRange( iTrack, iStartRow, iEndRow, const_begin, const_end ); - lBegin = const_begin; - lEnd = const_end; -} - - - -bool NoteData::GetNextTapNoteRowForAllTracks( int &rowInOut ) const -{ - int iClosestNextRow = MAX_NOTE_ROW; - bool bAnyHaveNextNote = false; - for( int t=0; tCreateNode(); - - p2->AppendAttr( "Track", iter.Track() ); - p2->AppendAttr( "Row", iter.Row() ); - p->AppendChild( p2 ); - } - return p; -} - -void NoteData::LoadFromNode( const XNode* pNode ) -{ - FAIL_M("NoteData::LoadFromNode() not implemented"); -} - -template -void NoteData::_all_tracks_iterator::Find( bool bReverse ) -{ - // If no notes can be found in the range, m_iTrack will stay -1 and IsAtEnd() will return true. - m_iTrack = -1; - if( bReverse ) - { - int iMaxRow = INT_MIN; - for( int iTrack = m_pNoteData->GetNumTracks() - 1; iTrack >= 0; --iTrack ) - { - iter &i( m_vCurrentIters[iTrack] ); - const iter &end = m_vEndIters[iTrack]; - if( i != end && i->first > iMaxRow ) - { - iMaxRow = i->first; - m_iTrack = iTrack; - } - } - } - else - { - - int iMinRow = INT_MAX; - for( int iTrack = 0; iTrack < m_pNoteData->GetNumTracks(); ++iTrack ) - { - iter &i = m_vCurrentIters[iTrack]; - const iter &end = m_vEndIters[iTrack]; - if( i != end && i->first < iMinRow ) - { - iMinRow = i->first; - m_iTrack = iTrack; - } - } - } -} - -template -NoteData::_all_tracks_iterator::_all_tracks_iterator( ND &nd, int iStartRow, int iEndRow, bool bReverse, bool bInclusive ) : - m_pNoteData(&nd), m_iTrack(0), m_bReverse(bReverse) -{ - ASSERT( m_pNoteData->GetNumTracks() > 0 ); - - for( int iTrack = 0; iTrack < m_pNoteData->GetNumTracks(); ++iTrack ) - { - iter begin, end; - if( bInclusive ) - m_pNoteData->GetTapNoteRangeInclusive( iTrack, iStartRow, iEndRow, begin, end ); - else - m_pNoteData->GetTapNoteRange( iTrack, iStartRow, iEndRow, begin, end ); - - m_vBeginIters.push_back( begin ); - m_vEndIters.push_back( end ); - - iter cur; - if( m_bReverse ) - { - cur = end; - if( cur != begin ) - cur--; - } - else - { - cur = begin; - } - m_vCurrentIters.push_back( cur ); - } - - Find( bReverse ); -} - -template -NoteData::_all_tracks_iterator::_all_tracks_iterator( const _all_tracks_iterator &other ) : -#define COPY_OTHER( x ) x( other.x ) - COPY_OTHER( m_pNoteData ), - COPY_OTHER( m_vBeginIters ), - COPY_OTHER( m_vCurrentIters ), - COPY_OTHER( m_vEndIters ), - COPY_OTHER( m_iTrack ), - COPY_OTHER( m_bReverse ) -#undef COPY_OTHER -{ -} - -template -NoteData::_all_tracks_iterator &NoteData::_all_tracks_iterator::operator++() // preincrement -{ - if( m_bReverse ) - { - if( m_vCurrentIters[m_iTrack] == m_vBeginIters[m_iTrack] ) - m_vCurrentIters[m_iTrack] = m_vEndIters[m_iTrack]; - else - --m_vCurrentIters[m_iTrack]; - } - else - { - ++m_vCurrentIters[m_iTrack]; - } - Find( m_bReverse ); - return *this; -} - -template -NoteData::_all_tracks_iterator NoteData::_all_tracks_iterator::operator++( int ) // postincrement -{ - _all_tracks_iterator ret( *this ); - operator++(); - return ret; -} -/* XXX: This doesn't satisfy the requirements that ++iter; --iter; is a no-op so it cannot be bidirectional for now. */ -#if 0 -template -NoteData::_all_tracks_iterator &NoteData::_all_tracks_iterator::operator--() // predecrement -{ - if( m_bReverse ) - { - ++m_vCurrentIters[m_iTrack]; - } - else - { - if( m_vCurrentIters[m_iTrack] == m_vEndIters[m_iTrack] ) - m_vCurrentIters[m_iTrack] = m_vEndIters[m_iTrack]; - else - --m_vCurrentIters[m_iTrack]; - } - Find( !m_bReverse ); - return *this; -} - -template -NoteData::_all_tracks_iterator NoteData::_all_tracks_iterator::operator--( int dummy ) // postdecrement -{ - _all_tracks_iterator ret( *this ); - operator--(); - return ret; -} -#endif - -// Explicit instantiation. -template class NoteData::_all_tracks_iterator; -template class NoteData::_all_tracks_iterator; - -/* - * (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. - */ +/* + * NoteData is organized by: + * track - corresponds to different columns of notes on the screen + * row/index - corresponds to subdivisions of beats + */ + +#include "global.h" +#include "NoteData.h" +#include "RageUtil.h" +#include "RageLog.h" +#include "XmlFile.h" +#include "GameState.h" // blame radar calculations. +#include "Foreach.h" +#include "RageUtil_AutoPtr.h" + +REGISTER_CLASS_TRAITS( NoteData, new NoteData(*pCopy) ) + +void NoteData::Init() +{ + m_TapNotes = vector(); // ensure that the memory is freed +} + +void NoteData::SetNumTracks( int iNewNumTracks ) +{ + ASSERT( iNewNumTracks > 0 ); + + m_TapNotes.resize( iNewNumTracks ); +} + +bool NoteData::IsComposite() const +{ + for( int track = 0; track < GetNumTracks(); ++track ) + { + for (std::pair const &tn : m_TapNotes[track]) + if( tn.second.pn != PLAYER_INVALID ) + return true; + } + return false; +} + +// Clear (rowBegin,rowEnd). +void NoteData::ClearRangeForTrack( int rowBegin, int rowEnd, int iTrack ) +{ + // Optimization: if the range encloses everything, just clear the whole maps. + if( rowBegin == 0 && rowEnd == MAX_NOTE_ROW ) + { + m_TapNotes[iTrack].clear(); + return; + } + + /* If the range is empty, don't do anything. Otherwise, an empty range will + * cause hold notes to be split when they shouldn't be. */ + if( rowBegin == rowEnd ) + return; + + NoteData::TrackMap::iterator lBegin, lEnd; + GetTapNoteRangeInclusive( iTrack, rowBegin, rowEnd, lBegin, lEnd ); + + if( lBegin != lEnd && lBegin->first < rowBegin && lBegin->first + lBegin->second.iDuration > rowEnd ) + { + /* A hold note overlaps the whole range. Truncate it, and add the + * remainder to the end. */ + TapNote tn1 = lBegin->second; + TapNote tn2 = tn1; + + int iEndRow = lBegin->first + tn1.iDuration; + int iRow = lBegin->first; + + tn1.iDuration = rowBegin - iRow; + tn2.iDuration = iEndRow - rowEnd; + + SetTapNote( iTrack, iRow, tn1 ); + SetTapNote( iTrack, rowEnd, tn2 ); + + // We may have invalidated our iterators. + GetTapNoteRangeInclusive( iTrack, rowBegin, rowEnd, lBegin, lEnd ); + } + else if( lBegin != lEnd && lBegin->first < rowBegin ) + { + // A hold note overlaps the beginning of the range. Truncate it. + TapNote &tn1 = lBegin->second; + int iRow = lBegin->first; + tn1.iDuration = rowBegin - iRow; + + ++lBegin; + } + + if( lBegin != lEnd ) + { + NoteData::TrackMap::iterator prev = lEnd; + --prev; + TapNote tn = lBegin->second; + int iRow = prev->first; + if( tn.type == TapNote::hold_head && iRow + tn.iDuration > rowEnd ) + { + // A hold note overlaps the end of the range. Separate it. + SetTapNote( iTrack, iRow, TAP_EMPTY ); + + int iAdd = rowEnd - iRow; + tn.iDuration -= iAdd; + iRow += iAdd; + SetTapNote( iTrack, iRow, tn ); + lEnd = prev; + } + + // We may have invalidated our iterators. + GetTapNoteRangeInclusive( iTrack, rowBegin, rowEnd, lBegin, lEnd ); + } + + m_TapNotes[iTrack].erase( lBegin, lEnd ); +} + +void NoteData::ClearRange( int rowBegin, int rowEnd ) +{ + for( int t=0; t < GetNumTracks(); ++t ) + ClearRangeForTrack( rowBegin, rowEnd, t ); +} + +void NoteData::ClearAll() +{ + for( int t=0; t rowFromEnd ) + return; // empty range + + const int rowToEnd = (rowFromEnd-rowFromBegin) + rowToBegin; + const int iMoveBy = rowToBegin-rowFromBegin; + + // Clear the region. + ClearRange( rowToBegin, rowToEnd ); + + for( int t=0; tsecond; + if( head.type == TapNote::empty ) + continue; + + if( head.type == TapNote::hold_head ) + { + int iStartRow = lBegin->first + iMoveBy; + int iEndRow = iStartRow + head.iDuration; + + iStartRow = clamp( iStartRow, rowToBegin, rowToEnd ); + iEndRow = clamp( iEndRow, rowToBegin, rowToEnd ); + + this->AddHoldNote( t, iStartRow, iEndRow, head ); + } + else + { + int iTo = lBegin->first + iMoveBy; + if( iTo >= rowToBegin && iTo <= rowToEnd ) + this->SetTapNote( t, iTo, head ); + } + } + } +} + +void NoteData::CopyAll( const NoteData& from ) +{ + *this = from; +} + +bool NoteData::IsRowEmpty( int row ) const +{ + for( int t=0; t& addTo ) const +{ + for( int t=0; t=0; t-- ) + { + if( GetTapNote( t, row ).type == TapNote::empty ) + { + iEmptyTrackOut = t; + return true; + } + } + return false; +} + +int NoteData::GetNumTracksWithTap( int row ) const +{ + int iNum = 0; + for( int t=0; t=0; t-- ) + { + const TapNote &tn = GetTapNote( t, row ); + if( tn.type == TapNote::tap || tn.type == TapNote::lift || tn.type == TapNote::hold_head ) + return t; + } + return -1; +} + +void NoteData::AddHoldNote( int iTrack, int iStartRow, int iEndRow, TapNote tn ) +{ + ASSERT( iStartRow>=0 && iEndRow>=0 ); + ASSERT_M( iEndRow >= iStartRow, ssprintf("EndRow %d < StartRow %d",iEndRow,iStartRow) ); + + /* Include adjacent (non-overlapping) hold notes, since we need to merge with them. */ + NoteData::TrackMap::iterator lBegin, lEnd; + GetTapNoteRangeInclusive( iTrack, iStartRow, iEndRow, lBegin, lEnd, true ); + + // Look for other hold notes that overlap and merge them into add. + for( iterator it = lBegin; it != lEnd; ++it ) + { + int iOtherRow = it->first; + const TapNote &tnOther = it->second; + if( tnOther.type == TapNote::hold_head ) + { + iStartRow = min( iStartRow, iOtherRow ); + iEndRow = max( iEndRow, iOtherRow + tnOther.iDuration ); + } + } + + tn.iDuration = iEndRow - iStartRow; + + // Remove everything in the range. + while( lBegin != lEnd ) + { + iterator next = lBegin; + ++next; + + RemoveTapNote( iTrack, lBegin ); + + lBegin = next; + } + + /* Additionally, if there's a tap note lying at the end of our range, + * remove it too. */ + SetTapNote( iTrack, iEndRow, TAP_EMPTY ); + + // add a tap note at the start of this hold + SetTapNote( iTrack, iStartRow, tn ); +} + +/* Determine if a hold note lies on the given spot. Return true if so. If + * pHeadRow is non-NULL, return the row of the head. */ +bool NoteData::IsHoldHeadOrBodyAtRow( int iTrack, int iRow, int *pHeadRow ) const +{ + const TapNote &tn = GetTapNote( iTrack, iRow ); + if( tn.type == TapNote::hold_head ) + { + if( pHeadRow != NULL ) + *pHeadRow = iRow; + return true; + } + + return IsHoldNoteAtRow( iTrack, iRow, pHeadRow ); +} + +/* Determine if a hold note lies on the given spot. Return true if so. If + * pHeadRow is non-NULL, return the row of the head. (Note that this returns + * false if a hold head lies on iRow itself.) */ +/* XXX: rename this to IsHoldBodyAtRow */ +bool NoteData::IsHoldNoteAtRow( int iTrack, int iRow, int *pHeadRow ) const +{ + int iDummy; + if( pHeadRow == NULL ) + pHeadRow = &iDummy; + + /* Starting at iRow, search upwards. If we find a TapNote::hold_head, we're within + * a hold. If we find a tap, mine or attack, we're not--those never lie + * within hold notes. Ignore autoKeysound. */ + FOREACH_NONEMPTY_ROW_IN_TRACK_RANGE_REVERSE( *this, iTrack, r, 0, iRow ) + { + const TapNote &tn = GetTapNote( iTrack, r ); + switch( tn.type ) + { + case TapNote::hold_head: + if( tn.iDuration + r < iRow ) + return false; + *pHeadRow = r; + return true; + + case TapNote::tap: + case TapNote::mine: + case TapNote::attack: + case TapNote::lift: + case TapNote::fake: + return false; + + case TapNote::empty: + case TapNote::autoKeysound: + // ignore + continue; + DEFAULT_FAIL( tn.type ); + } + } + + return false; +} + +bool NoteData::IsEmpty() const +{ + for( int t=0; t < GetNumTracks(); t++ ) + { + int iRow = -1; + if( !GetNextTapNoteRowForTrack( t, iRow ) ) + continue; + + return false; + } + + return true; +} + +int NoteData::GetFirstRow() const +{ + int iEarliestRowFoundSoFar = -1; + + for( int t=0; t < GetNumTracks(); t++ ) + { + int iRow = -1; + if( !GetNextTapNoteRowForTrack( t, iRow ) ) + continue; + + if( iEarliestRowFoundSoFar == -1 ) + iEarliestRowFoundSoFar = iRow; + else + iEarliestRowFoundSoFar = min( iEarliestRowFoundSoFar, iRow ); + } + + if( iEarliestRowFoundSoFar == -1 ) // there are no notes + return 0; + + return iEarliestRowFoundSoFar; +} + +int NoteData::GetLastRow() const +{ + int iOldestRowFoundSoFar = 0; + + for( int t=0; t < GetNumTracks(); t++ ) + { + int iRow = MAX_NOTE_ROW; + if( !GetPrevTapNoteRowForTrack( t, iRow ) ) + continue; + + /* XXX: We might have a hold note near the end with autoplay sounds + * after it. Do something else with autoplay sounds ... */ + const TapNote &tn = GetTapNote( t, iRow ); + if( tn.type == TapNote::hold_head ) + iRow += tn.iDuration; + + iOldestRowFoundSoFar = max( iOldestRowFoundSoFar, iRow ); + } + + return iOldestRowFoundSoFar; +} + +bool NoteData::IsTap(const TapNote &tn, const int row) const +{ + return (tn.type != TapNote::empty && tn.type != TapNote::mine + && tn.type != TapNote::lift && tn.type != TapNote::fake + && tn.type != TapNote::autoKeysound + && GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(row)); +} + +bool NoteData::IsMine(const TapNote &tn, const int row) const +{ + return (tn.type == TapNote::mine + && GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(row)); +} + +bool NoteData::IsLift(const TapNote &tn, const int row) const +{ + return (tn.type == TapNote::lift + && GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(row)); +} + +bool NoteData::IsFake(const TapNote &tn, const int row) const +{ + return (tn.type == TapNote::fake + || !GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(row)); +} + +int NoteData::GetNumTapNotes( int iStartIndex, int iEndIndex ) const +{ + int iNumNotes = 0; + for( int t=0; tIsTap(GetTapNote(t, r), r)) + iNumNotes++; + } + } + + return iNumNotes; +} + +int NoteData::GetNumTapNotesInRow( int iRow ) const +{ + int iNumNotes = 0; + for( int t=0; tIsTap(GetTapNote(t, iRow), iRow)) + iNumNotes++; + } + return iNumNotes; +} + +int NoteData::GetNumRowsWithTap( int iStartIndex, int iEndIndex ) const +{ + int iNumNotes = 0; + FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( *this, r, iStartIndex, iEndIndex ) + if( IsThereATapAtRow(r) && GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(r) ) + iNumNotes++; + + return iNumNotes; +} + +int NoteData::GetNumMines( int iStartIndex, int iEndIndex ) const +{ + int iNumMines = 0; + + for( int t=0; tIsMine(GetTapNote(t, r), r)) + iNumMines++; + } + + return iNumMines; +} + +int NoteData::GetNumRowsWithTapOrHoldHead( int iStartIndex, int iEndIndex ) const +{ + int iNumNotes = 0; + FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( *this, r, iStartIndex, iEndIndex ) + if( IsThereATapOrHoldHeadAtRow(r) && GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(r) ) + iNumNotes++; + + return iNumNotes; +} + +bool NoteData::RowNeedsAtLeastSimultaneousPresses( int iMinSimultaneousPresses, const int row ) const +{ + int iNumNotesThisIndex = 0; + for( int t=0; t= iMinSimultaneousPresses; +} + +int NoteData::GetNumRowsWithSimultaneousPresses( int iMinSimultaneousPresses, int iStartIndex, int iEndIndex ) const +{ + /* Count the number of times you have to use your hands. This includes + * three taps at the same time, a tap while two hold notes are being held, + * etc. Only count rows that have at least one tap note (hold heads count). + * Otherwise, every row of hold notes counts, so three simultaneous hold + * notes will count as hundreds of "hands". */ + int iNum = 0; + FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( *this, r, iStartIndex, iEndIndex ) + { + if( !RowNeedsAtLeastSimultaneousPresses(iMinSimultaneousPresses,r) ) + continue; + if (!GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(r)) + continue; + iNum++; + } + + return iNum; +} + +int NoteData::GetNumRowsWithSimultaneousTaps( int iMinTaps, int iStartIndex, int iEndIndex ) const +{ + int iNum = 0; + FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( *this, r, iStartIndex, iEndIndex ) + { + if (!GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(r)) + continue; + int iNumNotesThisIndex = 0; + for( int t=0; t= iMinTaps ) + iNum++; + } + + return iNum; +} + +int NoteData::GetNumHoldNotes( int iStartIndex, int iEndIndex ) const +{ + int iNumHolds = 0; + for( int t=0; tsecond.type != TapNote::hold_head || + lBegin->second.subType != TapNote::hold_head_hold ) + continue; + if (!GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(lBegin->first)) + continue; + iNumHolds++; + } + } + return iNumHolds; +} + +int NoteData::GetNumRolls( int iStartIndex, int iEndIndex ) const +{ + int iNumRolls = 0; + for( int t=0; tsecond.type != TapNote::hold_head || + lBegin->second.subType != TapNote::hold_head_roll ) + continue; + if (!GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(lBegin->first)) + continue; + iNumRolls++; + } + } + return iNumRolls; +} + +int NoteData::GetNumLifts( int iStartIndex, int iEndIndex ) const +{ + int iNumLifts = 0; + + for( int t=0; tIsLift(GetTapNote(t, r), r)) + iNumLifts++; + } + + return iNumLifts; +} + +int NoteData::GetNumFakes( int iStartIndex, int iEndIndex ) const +{ + int iNumFakes = 0; + + for( int t=0; tIsFake(GetTapNote(t, r), r)) + iNumFakes++; + } + + return iNumFakes; +} + +bool NoteData::IsPlayer1(const int track, const TapNote &tn) const +{ + if (this->IsComposite()) + { + return tn.pn == PLAYER_1; + } + return track < (this->GetNumTracks() / 2); +} + +pair NoteData::GetNumTapNotesTwoPlayer( int iStartIndex, int iEndIndex ) const +{ + pair num(0, 0); + for( int t=0; tIsTap(tn, r)) + { + if (this->IsPlayer1(t, tn)) + num.first++; + else + num.second++; + } + } + } + return num; +} + +pair NoteData::GetNumRowsWithSimultaneousTapsTwoPlayer(int minTaps, + int startRow, + int endRow) const +{ + pair num(0, 0); + FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( *this, r, startRow, endRow ) + { + pair found(0, 0); + for( int t=0; tIsTap(tn, r)) + { + if (this->IsPlayer1(t, tn)) + found.first++; + else + found.second++; + } + } + if (found.first >= minTaps) + num.first++; + if (found.second >= minTaps) + num.second++; + } + return num; +} + +pair NoteData::GetNumJumpsTwoPlayer( int iStartIndex, int iEndIndex ) const +{ + return GetNumRowsWithSimultaneousTapsTwoPlayer( 2, iStartIndex, iEndIndex ); +} + +pair NoteData::GetNumHandsTwoPlayer( int iStartIndex, int iEndIndex ) const +{ + return GetNumRowsWithSimultaneousTapsTwoPlayer( 3, iStartIndex, iEndIndex ); +} + +pair NoteData::GetNumQuadsTwoPlayer( int iStartIndex, int iEndIndex ) const +{ + return GetNumRowsWithSimultaneousTapsTwoPlayer( 4, iStartIndex, iEndIndex ); +} + +pair NoteData::GetNumHoldNotesTwoPlayer( int iStartIndex, int iEndIndex ) const +{ + pair num(0, 0); + for( int t=0; tsecond.type != TapNote::hold_head || + lBegin->second.subType != TapNote::hold_head_hold ) + continue; + if (!GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(lBegin->first)) + continue; + if (this->IsPlayer1(t, lBegin->second)) + num.first++; + else + num.second++; + } + } + return num; +} + +pair NoteData::GetNumMinesTwoPlayer( int iStartIndex, int iEndIndex ) const +{ + pair num(0, 0); + for( int t=0; tIsMine(tn, r)) + { + if (this->IsPlayer1(t, tn)) + num.first++; + else + num.second++; + } + } + } + return num; +} + +pair NoteData::GetNumRollsTwoPlayer( int iStartIndex, int iEndIndex ) const +{ + pair num(0, 0); + for( int t=0; tsecond.type != TapNote::hold_head || + lBegin->second.subType != TapNote::hold_head_roll ) + continue; + if (!GAMESTATE->GetProcessedTimingData()->IsJudgableAtRow(lBegin->first)) + continue; + if (this->IsPlayer1(t, lBegin->second)) + num.first++; + else + num.second++; + } + } + return num; +} + +pair NoteData::GetNumLiftsTwoPlayer( int iStartIndex, int iEndIndex ) const +{ + pair num(0, 0); + for( int t=0; tIsLift(tn, r)) + { + if (this->IsPlayer1(t, tn)) + num.first++; + else + num.second++; + } + } + } + return num; +} + +pair NoteData::GetNumFakesTwoPlayer( int iStartIndex, int iEndIndex ) const +{ + pair num(0, 0); + for( int t=0; tIsFake(tn, r)) + { + if (this->IsPlayer1(t, tn)) + num.first++; + else + num.second++; + } + } + } + return num; +} + +/* +int NoteData::GetNumMinefields( int iStartIndex, int iEndIndex ) const +{ + int iNumMinefields = 0; + for( int t=0; tsecond.type != TapNote::hold_head || + begin->second.subType != TapNote::hold_head_mine ) + continue; + iNumMinefields++; + } + } + return iNumMinefields; +} +*/ + +// -1 for iOriginalTracksToTakeFrom means no track +void NoteData::LoadTransformed( const NoteData& in, int iNewNumTracks, const int iOriginalTrackToTakeFrom[] ) +{ + // reset all notes + Init(); + + SetNumTracks( iNewNumTracks ); + + // copy tracks + for( int t=0; t= %i (#tracks) (taking from %i)", + iOriginalTrack, in.GetNumTracks(), iOriginalTrackToTakeFrom[t])); + + if( iOriginalTrack == -1 ) + continue; + m_TapNotes[t] = in.m_TapNotes[iOriginalTrack]; + } +} + +void NoteData::MoveTapNoteTrack( int dest, int src ) +{ + if(dest == src) return; + m_TapNotes[dest] = m_TapNotes[src]; + m_TapNotes[src].clear(); +} + +void NoteData::SetTapNote( int track, int row, const TapNote& t ) +{ + DEBUG_ASSERT( track>=0 && track& addTo ) +{ + for( int t=0; t viTracks; + viTracks.clear(); + GetTracksHeldAtRow( row, viTracks ); + return viTracks.size(); +} + +bool NoteData::GetNextTapNoteRowForTrack( int track, int &rowInOut ) const +{ + const TrackMap &mapTrack = m_TapNotes[track]; + + // lower_bound and upper_bound have the same effect here because duplicate + // keys aren't allowed. + + // lower_bound "finds the first element whose key is not less than k" (>=); + // upper_bound "finds the first element whose key greater than k". They don't + // have the same effect, but lower_bound(row+1) should equal upper_bound(row). -glenn + TrackMap::const_iterator iter = mapTrack.lower_bound( rowInOut+1 ); // "find the first note for which row+1 < key == false" + if( iter == mapTrack.end() ) + return false; + + ASSERT( iter->first > rowInOut ); + rowInOut = iter->first; + return true; +} + +bool NoteData::GetPrevTapNoteRowForTrack( int track, int &rowInOut ) const +{ + const TrackMap &mapTrack = m_TapNotes[track]; + + // Find the first note >= rowInOut. + TrackMap::const_iterator iter = mapTrack.lower_bound( rowInOut ); + + // If we're at the beginning, we can't move back any more. + if( iter == mapTrack.begin() ) + return false; + + // Move back by one. + --iter; + ASSERT( iter->first < rowInOut ); + rowInOut = iter->first; + return true; +} + +void NoteData::GetTapNoteRange( int iTrack, int iStartRow, int iEndRow, TrackMap::iterator &lBegin, TrackMap::iterator &lEnd ) +{ + ASSERT_M( iTrack < GetNumTracks(), ssprintf("%i,%i", iTrack, GetNumTracks()) ); + TrackMap &mapTrack = m_TapNotes[iTrack]; + + if( iStartRow > iEndRow ) + { + lBegin = lEnd = mapTrack.end(); + return; + } + + if( iStartRow <= 0 ) + lBegin = mapTrack.begin(); // optimization + else if( iStartRow >= MAX_NOTE_ROW ) + lBegin = mapTrack.end(); // optimization + else + lBegin = mapTrack.lower_bound( iStartRow ); + + if( iEndRow <= 0 ) + lEnd = mapTrack.begin(); // optimization + else if( iEndRow >= MAX_NOTE_ROW ) + lEnd = mapTrack.end(); // optimization + else + lEnd = mapTrack.lower_bound( iEndRow ); +} + + +/* Include hold notes that overlap the edges. If a hold note completely surrounds the given + * range, included it, too. If bIncludeAdjacent is true, also include hold notes adjacent to, + * but not overlapping, the edge. */ +void NoteData::GetTapNoteRangeInclusive( int iTrack, int iStartRow, int iEndRow, TrackMap::iterator &lBegin, TrackMap::iterator &lEnd, bool bIncludeAdjacent ) +{ + GetTapNoteRange( iTrack, iStartRow, iEndRow, lBegin, lEnd ); + + if( lBegin != this->begin(iTrack) ) + { + iterator prev = Decrement(lBegin); + + const TapNote &tn = prev->second; + if( tn.type == TapNote::hold_head ) + { + int iHoldStartRow = prev->first; + int iHoldEndRow = iHoldStartRow + tn.iDuration; + if( bIncludeAdjacent ) + ++iHoldEndRow; + if( iHoldEndRow > iStartRow ) + { + // The previous note is a hold. + lBegin = prev; + } + } + } + + if( bIncludeAdjacent && lEnd != this->end(iTrack) ) + { + // Include the next note if it's a hold and starts on iEndRow. + const TapNote &tn = lEnd->second; + int iHoldStartRow = lEnd->first; + if( tn.type == TapNote::hold_head && iHoldStartRow == iEndRow ) + ++lEnd; + } +} + +void NoteData::GetTapNoteRangeExclusive( int iTrack, int iStartRow, int iEndRow, TrackMap::iterator &lBegin, TrackMap::iterator &lEnd ) +{ + GetTapNoteRange( iTrack, iStartRow, iEndRow, lBegin, lEnd ); + + // If end-1 is a hold_head, and extends beyond iEndRow, exclude it. + if( lBegin != lEnd && lEnd != this->begin(iTrack) ) + { + iterator prev = lEnd; + --prev; + if( prev->second.type == TapNote::hold_head ) + { + int localStartRow = prev->first; + const TapNote &tn = prev->second; + if( localStartRow + tn.iDuration >= iEndRow ) + lEnd = prev; + } + } +} + +void NoteData::GetTapNoteRange( int iTrack, int iStartRow, int iEndRow, TrackMap::const_iterator &lBegin, TrackMap::const_iterator &lEnd ) const +{ + TrackMap::iterator const_begin, const_end; + const_cast(this)->GetTapNoteRange( iTrack, iStartRow, iEndRow, const_begin, const_end ); + lBegin = const_begin; + lEnd = const_end; +} + +void NoteData::GetTapNoteRangeInclusive( int iTrack, int iStartRow, int iEndRow, TrackMap::const_iterator &lBegin, TrackMap::const_iterator &lEnd, bool bIncludeAdjacent ) const +{ + TrackMap::iterator const_begin, const_end; + const_cast(this)->GetTapNoteRangeInclusive( iTrack, iStartRow, iEndRow, const_begin, const_end, bIncludeAdjacent ); + lBegin = const_begin; + lEnd = const_end; +} + +void NoteData::GetTapNoteRangeExclusive( int iTrack, int iStartRow, int iEndRow, TrackMap::const_iterator &lBegin, TrackMap::const_iterator &lEnd ) const +{ + TrackMap::iterator const_begin, const_end; + const_cast(this)->GetTapNoteRange( iTrack, iStartRow, iEndRow, const_begin, const_end ); + lBegin = const_begin; + lEnd = const_end; +} + + + +bool NoteData::GetNextTapNoteRowForAllTracks( int &rowInOut ) const +{ + int iClosestNextRow = MAX_NOTE_ROW; + bool bAnyHaveNextNote = false; + for( int t=0; tCreateNode(); + + p2->AppendAttr( "Track", iter.Track() ); + p2->AppendAttr( "Row", iter.Row() ); + p->AppendChild( p2 ); + } + return p; +} + +void NoteData::LoadFromNode( const XNode* pNode ) +{ + FAIL_M("NoteData::LoadFromNode() not implemented"); +} + +template +void NoteData::_all_tracks_iterator::Find( bool bReverse ) +{ + // If no notes can be found in the range, m_iTrack will stay -1 and IsAtEnd() will return true. + m_iTrack = -1; + if( bReverse ) + { + int iMaxRow = INT_MIN; + for( int iTrack = m_pNoteData->GetNumTracks() - 1; iTrack >= 0; --iTrack ) + { + iter &i( m_vCurrentIters[iTrack] ); + const iter &end = m_vEndIters[iTrack]; + if( i != end && i->first > iMaxRow ) + { + iMaxRow = i->first; + m_iTrack = iTrack; + } + } + } + else + { + + int iMinRow = INT_MAX; + for( int iTrack = 0; iTrack < m_pNoteData->GetNumTracks(); ++iTrack ) + { + iter &i = m_vCurrentIters[iTrack]; + const iter &end = m_vEndIters[iTrack]; + if( i != end && i->first < iMinRow ) + { + iMinRow = i->first; + m_iTrack = iTrack; + } + } + } +} + +template +NoteData::_all_tracks_iterator::_all_tracks_iterator( ND &nd, int iStartRow, int iEndRow, bool bReverse, bool bInclusive ) : + m_pNoteData(&nd), m_iTrack(0), m_bReverse(bReverse) +{ + ASSERT( m_pNoteData->GetNumTracks() > 0 ); + + for( int iTrack = 0; iTrack < m_pNoteData->GetNumTracks(); ++iTrack ) + { + iter begin, end; + if( bInclusive ) + m_pNoteData->GetTapNoteRangeInclusive( iTrack, iStartRow, iEndRow, begin, end ); + else + m_pNoteData->GetTapNoteRange( iTrack, iStartRow, iEndRow, begin, end ); + + m_vBeginIters.push_back( begin ); + m_vEndIters.push_back( end ); + + iter cur; + if( m_bReverse ) + { + cur = end; + if( cur != begin ) + cur--; + } + else + { + cur = begin; + } + m_vCurrentIters.push_back( cur ); + } + + Find( bReverse ); +} + +template +NoteData::_all_tracks_iterator::_all_tracks_iterator( const _all_tracks_iterator &other ) : +#define COPY_OTHER( x ) x( other.x ) + COPY_OTHER( m_pNoteData ), + COPY_OTHER( m_vBeginIters ), + COPY_OTHER( m_vCurrentIters ), + COPY_OTHER( m_vEndIters ), + COPY_OTHER( m_iTrack ), + COPY_OTHER( m_bReverse ) +#undef COPY_OTHER +{ +} + +template +NoteData::_all_tracks_iterator &NoteData::_all_tracks_iterator::operator++() // preincrement +{ + if( m_bReverse ) + { + if( m_vCurrentIters[m_iTrack] == m_vBeginIters[m_iTrack] ) + m_vCurrentIters[m_iTrack] = m_vEndIters[m_iTrack]; + else + --m_vCurrentIters[m_iTrack]; + } + else + { + ++m_vCurrentIters[m_iTrack]; + } + Find( m_bReverse ); + return *this; +} + +template +NoteData::_all_tracks_iterator NoteData::_all_tracks_iterator::operator++( int ) // postincrement +{ + _all_tracks_iterator ret( *this ); + operator++(); + return ret; +} +/* XXX: This doesn't satisfy the requirements that ++iter; --iter; is a no-op so it cannot be bidirectional for now. */ +#if 0 +template +NoteData::_all_tracks_iterator &NoteData::_all_tracks_iterator::operator--() // predecrement +{ + if( m_bReverse ) + { + ++m_vCurrentIters[m_iTrack]; + } + else + { + if( m_vCurrentIters[m_iTrack] == m_vEndIters[m_iTrack] ) + m_vCurrentIters[m_iTrack] = m_vEndIters[m_iTrack]; + else + --m_vCurrentIters[m_iTrack]; + } + Find( !m_bReverse ); + return *this; +} + +template +NoteData::_all_tracks_iterator NoteData::_all_tracks_iterator::operator--( int dummy ) // postdecrement +{ + _all_tracks_iterator ret( *this ); + operator--(); + return ret; +} +#endif + +// Explicit instantiation. +template class NoteData::_all_tracks_iterator; +template class NoteData::_all_tracks_iterator; + +/* + * (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. + */