void NoteData::Convert2sAnd3sToHoldNotes() { // Any note will end a hold (not just a TAP_HOLD_TAIL). This makes parsing DWIs much easier. // Plus, allowing tap notes in the middle of a hold doesn't make sense! int rows = GetLastRow(); for( int col=0; col<m_iNumTracks; col++ ) // foreach column { for( int i=0; i<=rows; i++ ) // foreach TapNote element { if( GetTapNote(col,i).type != TapNote::hold_head ) // this is a HoldNote begin marker continue; SetTapNote(col, i, TAP_EMPTY); // clear the hold head marker for( int j=i+1; j<=rows; j++ ) // search for end of HoldNote { // End hold on the next note we see. This should be a hold_tail if the // data is in a consistent state, but doesn't have to be. if( GetTapNote(col, j).type == TapNote::empty ) continue; SetTapNote(col, j, TAP_EMPTY); AddHoldNote( HoldNote(col, i, j) ); break; // done searching for the end of this hold } } } }
int NoteDataWithScoring::GetNumNWithScore( TapNoteScore tns, int MinTaps, const float fStartBeat, float fEndBeat ) const { if( fEndBeat == -1 ) fEndBeat = GetNumBeats(); int iStartIndex = BeatToNoteRow( fStartBeat ); int iEndIndex = BeatToNoteRow( fEndBeat ); iStartIndex = max( iStartIndex, 0 ); iEndIndex = min( iEndIndex, GetNumRows()-1 ); int iNumSuccessfulDoubles = 0; for( int i=iStartIndex; i<=iEndIndex; i++ ) { int iNumNotesThisIndex = 0; TapNoteScore minTapNoteScore = TNS_MARVELOUS; for( int t=0; t<GetNumTracks(); t++ ) { switch( GetTapNote(t, i).type ) { case TapNote::tap: case TapNote::hold_head: iNumNotesThisIndex++; minTapNoteScore = min( minTapNoteScore, GetTapNoteScore(t, i) ); } } if( iNumNotesThisIndex >= MinTaps && minTapNoteScore >= tns ) iNumSuccessfulDoubles++; } return iNumSuccessfulDoubles; }
/* Return the last tap score of a row: the grade of the tap that completed * the row. If the row has no tap notes, return -1. If any tap notes aren't * graded (any tap is TNS_NONE) or are missed (TNS_MISS), return it. */ int NoteDataWithScoring::LastTapNoteScoreTrack(unsigned row) const { float scoretime = -9999; int best_track = -1; for( int t=0; t<GetNumTracks(); t++ ) { /* Skip empty tracks and mines */ TapNote tn = GetTapNote(t, row); if( tn.type == TapNote::empty || tn.type == TapNote::mine ) continue; TapNoteScore tns = GetTapNoteScore(t, row); if( tns == TNS_MISS || tns == TNS_NONE ) return t; float tm = GetTapNoteOffset(t, row); if(tm < scoretime) continue; scoretime = tm; best_track = t; } return best_track; }
void NoteData::PruneUnusedAttacksFromMap() { // Add all used AttackNote index values to a map. set<unsigned> setUsedIndices; int num_rows = GetNumRows(); for( int t=0; t<m_iNumTracks; t++ ) { for( int r=0; r<num_rows; r++ ) { TapNote tn = GetTapNote(t, r); if( tn.type == TapNote::attack ) setUsedIndices.insert( tn.attackIndex ); } } // Remove all items from m_AttackMap that don't have corresponding // TapNotes in use. for( unsigned i=0; i<MAX_NUM_ATTACKS; i++ ) { bool bInAttackMap = m_AttackMap.find(i) != m_AttackMap.end(); bool bActuallyUsed = setUsedIndices.find(i) != setUsedIndices.end(); if( bActuallyUsed && !bInAttackMap ) ASSERT(0); // something earlier than us didn't enforce consistency if( bInAttackMap && !bActuallyUsed ) m_AttackMap.erase( i ); } }
int NoteData::GetNumTapNonEmptyTracks( int index ) const { int iNum = 0; for( int t=0; t<m_iNumTracks; t++ ) if( GetTapNote(t, index).type != TapNote::empty ) iNum++; return iNum; }
/* "104444001" == * "102000301" * * "4441" basically means "hold for three rows then hold for another tap"; * since taps don't really have a length, it's equivalent to "4440". * So, make sure the character after a 4 is always a 0. */ void NoteData::Convert4sToHoldNotes() { int rows = GetLastRow(); for( int col=0; col<m_iNumTracks; col++ ) // foreach column { for( int i=0; i<=rows; i++ ) // foreach TapNote element { if( GetTapNote(col, i).type == TapNote::hold ) // this is a HoldNote body { HoldNote hn( col, i, 0 ); // search for end of HoldNote do { SetTapNote(col, i, TAP_EMPTY); i++; } while( GetTapNote(col, i).type == TapNote::hold ); SetTapNote(col, i, TAP_EMPTY); hn.iEndRow = i; AddHoldNote( hn ); } } } }
/* Return the minimum tap score of a row. If the row isn't complete (not all * taps have been hit), return TNS_NONE or TNS_MISS. */ TapNoteScore NoteDataWithScoring::MinTapNoteScore(unsigned row) const { TapNoteScore score = TNS_MARVELOUS; for( int t=0; t<GetNumTracks(); t++ ) { /* Don't coun, or else the score * will always be TNS_NONE. */ TapNote tn = GetTapNote(t, row); if( tn.type == TapNote::empty || tn.type == TapNote::mine) continue; score = min( score, GetTapNoteScore(t, row) ); } return score; }
const Attack& NoteData::GetAttackAt( int track, int row ) { TapNote tn = GetTapNote(track, row); ASSERT( tn.type == TapNote::attack ); // don't call this if the TapNote here isn't an attack map<unsigned,Attack>::iterator iter = m_AttackMap.find( tn.attackIndex ); /* Hack: if referencing an attack that doesn't exist, add it. This is just * to prevent crashes. This hack isn't needed in newer versions, since this * interface is gone. */ if( iter == m_AttackMap.end() ) { m_AttackMap[tn.attackIndex] = Attack(); iter = m_AttackMap.find( tn.attackIndex ); ASSERT( iter != m_AttackMap.end() ); } return iter->second; }
void NoteData::GetTapNonEmptyTracks( int index, set<int>& addTo ) const { for( int t=0; t<m_iNumTracks; t++ ) if( GetTapNote(t, index).type != TapNote::empty ) addTo.insert(t); }