/* * 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" NoteData::NoteData() { Init(); } void NoteData::Init() { ClearAll(); m_TapNotes.clear(); } NoteData::~NoteData() { } void NoteData::SetNumTracks( int iNewNumTracks ) { ASSERT( iNewNumTracks > 0 ); m_TapNotes.resize( iNewNumTracks ); /* Remove all hold notes that are out of bounds. */ // Iterate backwards so that we can delete. for( int h = m_HoldNotes.size()-1; h >= 0; --h ) if( m_HoldNotes[h].iTrack >= iNewNumTracks ) m_HoldNotes.erase( m_HoldNotes.begin()+h ); } /* Clear [rowBegin,rowEnd]; that is, including 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(); for( int i = GetNumHoldNotes()-1; i >= 0; --i ) { HoldNote hn = GetHoldNote(i); if( hn.iTrack != iTrack ) continue; this->RemoveHoldNote( i ); } } /* Crop or split hold notes overlapping the range. */ for( int i = GetNumHoldNotes()-1; i >= 0; --i ) // for each HoldNote { HoldNote hn = GetHoldNote(i); if( hn.iTrack != iTrack ) continue; if( !hn.RangeOverlaps(rowBegin, rowEnd) ) continue; this->RemoveHoldNote( i ); /* If the range encloses the hold note completely, just delete it. */ if( hn.ContainedByRange(rowBegin, rowEnd) ) continue; if( hn.RangeInside(rowBegin, rowEnd) ) { /* The hold note encloses the range, so we need to split the hold note. */ HoldNote hnLater(hn); hn.iEndRow = rowBegin; hnLater.iStartRow = rowEnd; this->AddHoldNote( hn ); this->AddHoldNote( hnLater ); continue; } if( hn.iStartRow < rowBegin ) hn.iEndRow = min( hn.iEndRow, rowBegin ); else hn.iStartRow = max( hn.iStartRow, rowEnd ); this->AddHoldNote( hn ); } /* Clear other notes in the region. */ FOREACH_NONEMPTY_ROW_IN_TRACK_RANGE( *this, iTrack, r, rowBegin, rowEnd ) SetTapNote( iTrack, r, TAP_EMPTY ); } 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; tSetTapNote( t, iTo, tn ); } } /* Copy hold notes. */ for( int i=0; iGetNumHoldNotes(); ++j ) // for each HoldNote { const HoldNote &hn2 = this->GetHoldNote(j); if( hn2.iEndRow == hn.iStartRow ) iEarlierHoldNote = j; if( hn2.iStartRow == hn.iEndRow ) iLaterHoldNote = j; } if( iEarlierHoldNote != -1 && iLaterHoldNote != -1 ) { HoldNote &hnEarlier = this->GetHoldNote( iEarlierHoldNote ); const HoldNote &hnLater = this->GetHoldNote( iLaterHoldNote ); hnEarlier.iEndRow = hnLater.iEndRow; this->RemoveHoldNote( iLaterHoldNote ); } else if( iEarlierHoldNote == -1 && iLaterHoldNote == -1 ) AddHoldNote( hn ); else if( iEarlierHoldNote != -1 ) { HoldNote &hn2 = this->GetHoldNote(iEarlierHoldNote); hn2.iEndRow = hn.iEndRow; } else if( iLaterHoldNote != -1 ) { HoldNote &hn2 = this->GetHoldNote(iLaterHoldNote); hn2.iStartRow = hn.iStartRow; } else FAIL_M(ssprintf("%i,%i", iEarlierHoldNote, iLaterHoldNote)); } } void NoteData::Config( const NoteData& from ) { SetNumTracks( from.GetNumTracks() ); } void NoteData::CopyAll( const NoteData& from ) { Config(from); ClearAll(); for( int c=0; c& 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 && add.iEndRow>=0 ); // look for other hold notes that overlap and merge them for( int i=0; i= 0 && iHoldIndex < GetNumHoldNotes() ); HoldNote& hn = GetHoldNote(iHoldIndex); const int iHoldStartIndex = hn.iStartRow; // delete a tap note at the start of this hold SetTapNote(hn.iTrack, iHoldStartIndex, TAP_EMPTY); // remove from list m_HoldNotes.erase(m_HoldNotes.begin()+iHoldIndex, m_HoldNotes.begin()+iHoldIndex+1); } /* Return true if a hold note lies on the given spot. Must be in 2sAnd3s. */ bool NoteData::IsHoldNoteAtBeat( int iTrack, int iRow, int *pHeadRow, int *pTailRow ) const { /* If neither the actual head nor tail row were requested, search for the head to * determine the return value. */ int iDummy; if( pHeadRow == NULL ) pHeadRow = &iDummy; bool bFoundHead = false; if( pHeadRow != NULL ) { /* 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: *pHeadRow = r; bFoundHead = true; break; case TapNote::tap: case TapNote::mine: case TapNote::attack: case TapNote::hold_tail: return false; case TapNote::empty: case TapNote::autoKeysound: /* ignore */ continue; case TapNote::hold: /* Don't call this function when in 4s mode! */ FAIL_M("hold"); default: FAIL_M( ssprintf("%i", tn.type) ); } if( bFoundHead ) break; } /* If we didn't find a matching head, we're not within a hold note, so don't bother * searching for a tail. */ if( !bFoundHead ) return false; } bool bFoundTail = false; if( pTailRow != NULL ) { FOREACH_NONEMPTY_ROW_IN_TRACK_RANGE( *this, iTrack, r, iRow, MAX_NOTE_ROW ) { const TapNote &tn = GetTapNote( iTrack, r ); switch( tn.type ) { case TapNote::hold_tail: *pTailRow = r; bFoundTail = true; break; case TapNote::tap: case TapNote::mine: case TapNote::attack: return false; case TapNote::hold_head: /* If iRow is a hold head, we're within a hold note, and need to continue searching; * if any row after that is a head, we're not in it, so stop. This is different * than above, since holds are [head,tail); the row of the tail isn't actually * part of the hold. */ if( r == iRow ) continue; return false; case TapNote::empty: case TapNote::autoKeysound: /* ignore */ continue; case TapNote::hold: /* Don't call this function when in 4s mode! */ FAIL_M("hold"); default: FAIL_M( ssprintf("%i", tn.type) ); } if( bFoundTail ) break; return true; } } return bFoundHead || bFoundTail; } 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 ); } for( int i=0; i iOldestRowFoundSoFar ) iOldestRowFoundSoFar = GetHoldNote(i).iEndRow; } return iOldestRowFoundSoFar; } int NoteData::GetNumTapNotes( int iStartIndex, int iEndIndex ) const { int iNumNotes = 0; for( int t=0; t= 3; } int NoteData::GetNumHands( 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( !RowNeedsHands(r) ) continue; iNum++; } return iNum; } int NoteData::GetNumN( int iMinTaps, int iStartIndex, int iEndIndex ) const { int iNum = 0; FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( *this, r, iStartIndex, iEndIndex ) { 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 i=0; i= %i (to %i)", iOriginalTrack, in.GetNumTracks(), iOriginalTrackToTakeFrom[t])); if( iOriginalTrack == -1 ) continue; m_TapNotes[t] = Input.m_TapNotes[iOriginalTrack]; } Convert2sAnd3sToHoldNotes(); } 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( unsigned i=0; i 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; } /* Return an iterator range. This can be used to iterate trackwise over a range of * notes. It's like FOREACH_NONEMPTY_ROW_IN_TRACK_RANGE, except it only requires * two map searches (iterating is O(1)), but the iterators will become invalid if * the notes they represent disappear, so you need to pay attention to how you modify * the data. */ void NoteData::GetTapNoteRange( int iTrack, int iStartRow, int iEndRow, TrackMap::const_iterator &begin, TrackMap::const_iterator &end ) const { ASSERT_M( iTrack < GetNumTracks(), ssprintf("%i,%i", iTrack, GetNumTracks()) ); ASSERT_M( iStartRow <= iEndRow, ssprintf("%i > %i", iStartRow, iEndRow) ); const TrackMap &mapTrack = m_TapNotes[iTrack]; begin = mapTrack.lower_bound( iStartRow ); end = mapTrack.upper_bound( iEndRow ); } bool NoteData::GetNextTapNoteRowForAllTracks( int &rowInOut ) const { int iClosestNextRow = MAX_NOTE_ROW; bool bAnyHaveNextNote = false; for( int t=0; t