#include "global.h" /* ----------------------------------------------------------------------------- Class: NoteData Desc: See header. Copyright (c) 2001-2002 by the person(s) listed below. All rights reserved. Chris Danford ----------------------------------------------------------------------------- */ #include "NoteData.h" #include "RageUtil.h" #include "RageLog.h" NoteData::NoteData() { m_iNumTracks = 0; Init(); } void NoteData::Init() { ClearAll(); m_iNumTracks = 0; // must do this after calling ClearAll()! } NoteData::~NoteData() { } int NoteData::GetNumTracks() const { return m_iNumTracks; } void NoteData::SetNumTracks( int iNewNumTracks ) { m_iNumTracks = iNewNumTracks; // Make sure that all tracks are of the same length ASSERT( m_iNumTracks > 0 ); int rows = m_TapSteps[0].size(); for( int t=0; t= 0; --h ) if( m_HoldNotes[h].iTrack >= iNewNumTracks ) m_HoldNotes.erase( m_HoldNotes.begin()+h ); } /* Clear [iNoteIndexBegin,iNoteIndexEnd]; that is, including iNoteIndexEnd. */ void NoteData::ClearRange( int iNoteIndexBegin, int iNoteIndexEnd ) { this->ConvertHoldNotesTo4s(); for( int c=0; cConvert4sToHoldNotes(); } void NoteData::ClearAll() { for( int t=0; tm_iNumTracks == m_iNumTracks ); NoteData From, To; From.To4s( *pFrom ); To.To4s( *this ); // copy recorded TapSteps int f = iFromIndexBegin, t = iToIndexBegin; while( f<=iFromIndexEnd ) { for( int c=0; cFrom4s( To ); } void NoteData::Config( const NoteData &From ) { m_iNumTracks = From.m_iNumTracks; } void NoteData::CopyAll( const NoteData* pFrom ) { Config(*pFrom); ClearAll(); for( int c=0; cm_TapSteps[c]; m_HoldNotes = pFrom->m_HoldNotes; m_AttackMap = pFrom->m_AttackMap; } bool NoteData::IsRowEmpty( int index ) const { /* If this is out of range, we don't have any notes there, so all tracks are empty. */ if( index < 0 || index >= GetMaxRow() ) return true; for( int t=0; t& addTo ) const { for( int t=0; t= GetMaxRow() ) return 0; for( int t=0; t= GetMaxRow() ) return 0; int iNum = 0; for( int t=0; t= GetMaxRow() ) return 0; int iNum = 0; for( int t=0; t= GetMaxRow() ) return -1; for( int t=0; t= GetMaxRow() ) return -1; for( int t=0; t=0 && add.iEndRow>=0 ); int i; // look for other hold notes that overlap and merge them // XXX: this is done implicitly with 4s, but 4s uses this function. // Rework this later. for( 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); } /* Get a hold note with the same track and end row as hn. */ int NoteData::GetMatchingHoldNote( const HoldNote &hn ) const { for( int i=0; i mapAttackToNothing; int max_row = GetMaxRow(); for( int t=0; t=0; i-- ) // iterate back to front { if( !IsRowEmpty(i) ) { iOldestRowFoundSoFar = i; break; } } for( i=0; i iOldestRowFoundSoFar ) iOldestRowFoundSoFar = GetHoldNote(i).iEndRow; } return iOldestRowFoundSoFar; } float NoteData::GetLastBeat() const { return NoteRowToBeat( GetLastRow() ); } int NoteData::GetNumTapSteps( float fStartBeat, float fEndBeat ) const { int iNumNotes = 0; if( fEndBeat == -1 ) fEndBeat = GetMaxBeat(); int iStartIndex = BeatToNoteRow( fStartBeat ); int iEndIndex = BeatToNoteRow( fEndBeat ); /* Clamp to known-good ranges. */ iStartIndex = max( iStartIndex, 0 ); iEndIndex = min( iEndIndex, GetMaxRow()-1 ); for( int t=0; t= 3; } int NoteData::GetNumHands( float fStartBeat, float fEndBeat ) 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". */ if( fEndBeat == -1 ) fEndBeat = GetMaxBeat(); int iStartIndex = BeatToNoteRow( fStartBeat ); int iEndIndex = BeatToNoteRow( fEndBeat ); /* Clamp to known-good ranges. */ iStartIndex = max( iStartIndex, 0 ); iEndIndex = min( iEndIndex, GetMaxRow()-1 ); int iNum = 0; for( int i=iStartIndex; i<=iEndIndex; i++ ) { if( !RowNeedsHands(i) ) continue; iNum++; } return iNum; } int NoteData::GetNumN( int MinTaps, float fStartBeat, float fEndBeat ) const { if( fEndBeat == -1 ) fEndBeat = GetMaxBeat(); int iStartIndex = BeatToNoteRow( fStartBeat ); int iEndIndex = BeatToNoteRow( fEndBeat ); /* Clamp to known-good ranges. */ iStartIndex = max( iStartIndex, 0 ); iEndIndex = min( iEndIndex, GetMaxRow()-1 ); int iNum = 0; for( int i=iStartIndex; i<=iEndIndex; i++ ) { int iNumNotesThisIndex = 0; for( int t=0; t= MinTaps ) iNum++; } return iNum; } int NoteData::GetNumHoldNotes( float fStartBeat, float fEndBeat ) const { if( fEndBeat == -1 ) fEndBeat = GetMaxBeat(); int iStartIndex = BeatToNoteRow( fStartBeat ); int iEndIndex = BeatToNoteRow( fEndBeat ); int iNumHolds = 0; for( int i=0; i= %i (to %i)", iOriginalTrack, Original.m_iNumTracks, iOriginalTrackToTakeFrom[t])); if( iOriginalTrack == -1 ) continue; m_TapSteps[t] = Original.m_TapSteps[iOriginalTrack]; } Convert4sToHoldNotes(); m_AttackMap = Original.GetAttackMap(); } void NoteData::LoadOverlapped( const NoteData* pOriginal, int iNewNumTracks ) { SetNumTracks( iNewNumTracks ); NoteData in; in.To4s( *pOriginal ); /* Keep track of the last source track that put a tap into each destination track, * and the row of that tap. Then, if two rows are trying to put taps into the * same row within the shift threshold, shift the newcomer source row. */ int LastSourceTrack[MAX_NOTE_TRACKS]; int LastSourceRow[MAX_NOTE_TRACKS]; int DestRow[MAX_NOTE_TRACKS]; for( unsigned tr = 0; tr < MAX_NOTE_TRACKS; ++tr ) { LastSourceTrack[tr] = -1; LastSourceRow[tr] = -9999; DestRow[tr] = tr; wrap( DestRow[tr], iNewNumTracks ); } const int ShiftThreshold = BeatToNoteRow(1); const int iLastRow = in.GetMaxRow(); for( int row = 0; row < iLastRow; ++row ) { for ( int i = 0; i < in.GetNumTracks(); i++ ) { const int iTrackFrom = i; int &iTrackTo = DestRow[i]; const TapNote iStepFrom = in.m_TapSteps[iTrackFrom][row]; if( iStepFrom == TAP_EMPTY ) continue; if( LastSourceTrack[iTrackTo] != iTrackFrom && row - LastSourceRow[iTrackTo] < ShiftThreshold ) { /* This destination track is in use by a different source track. Use the * least-recently-used track. */ for( int DestTrack = 0; DestTrack < iNewNumTracks; ++DestTrack ) if( LastSourceRow[DestTrack] < LastSourceRow[iTrackTo] ) iTrackTo = DestTrack; } /* If it's still in use, then we just don't have an available track. */ if( LastSourceTrack[iTrackTo] != iTrackFrom && row - LastSourceRow[iTrackTo] < ShiftThreshold ) continue; LastSourceTrack[iTrackTo] = iTrackFrom; LastSourceRow[iTrackTo] = row; SetTapNote( iTrackTo, row, iStepFrom ); } } Convert4sToHoldNotes(); } void NoteData::LoadTransformedSlidingWindow( const NoteData* pOriginal, int iNewNumTracks ) { // reset all notes Init(); if( pOriginal->GetNumTracks() > iNewNumTracks ) { /* Use a different algorithm for reducing tracks. */ LoadOverlapped( pOriginal, iNewNumTracks ); return; } NoteData Original; Original.To4s( *pOriginal ); Config(*pOriginal); m_iNumTracks = iNewNumTracks; int iCurTrackOffset = 0; int iTrackOffsetMin = 0; int iTrackOffsetMax = abs( iNewNumTracks - Original.m_iNumTracks ); int bOffsetIncreasing = true; int iLastRow = Original.GetLastRow(); for( int r=0; r<=iLastRow; ) { // copy notes in this measure for( int t=0; tSetTapNote(iNewTrack, r, Original.GetTapNote(iOldTrack, r)); } r++; if( r % (ROWS_PER_MEASURE*4) == 0 ) // adjust sliding window every 4 measures { // See if there is a hold crossing the beginning of this measure bool bHoldCrossesThisMeasure = false; if( r ) for( int t=0; t<=Original.m_iNumTracks; t++ ) { if( Original.GetTapNote(t, r) == TAP_HOLD && Original.GetTapNote(t, r-1) == TAP_HOLD) { bHoldCrossesThisMeasure = true; break; } } // adjust offset if( !bHoldCrossesThisMeasure ) { iCurTrackOffset += bOffsetIncreasing ? 1 : -1; if( iCurTrackOffset == iTrackOffsetMin || iCurTrackOffset == iTrackOffsetMax ) bOffsetIncreasing ^= true; CLAMP( iCurTrackOffset, iTrackOffsetMin, iTrackOffsetMax ); } } } Convert4sToHoldNotes(); m_AttackMap = Original.GetAttackMap(); } void NoteData::PadTapSteps(int rows) { int needed = rows - m_TapSteps[0].size() + 1; if(needed < 0) return; needed += 100; /* optimization: give it a little more than it needs */ for(int track = 0; track < m_iNumTracks; ++track) m_TapSteps[track].insert(m_TapSteps[track].end(), needed, TAP_EMPTY); } void NoteData::MoveTapNoteTrack(int dest, int src) { if(dest == src) return; m_TapSteps[dest] = m_TapSteps[src]; m_TapSteps[src].clear(); } void NoteData::SetTapNote(int track, int row, TapNote t) { if(row < 0) return; PadTapSteps(row); m_TapSteps[track][row]=t; } void NoteData::ReserveRows( int row ) { for(int track = 0; track < m_iNumTracks; ++track) m_TapSteps[track].reserve( row ); } void NoteData::EliminateAllButOneTap(int row) { if(row < 0) return; PadTapSteps(row); int track; for(track = 0; track < m_iNumTracks; ++track) { if( m_TapSteps[track][row] == TAP_TAP ) break; } track++; for( ; track < m_iNumTracks; ++track) { if( m_TapSteps[track][row] == TAP_TAP ) m_TapSteps[track][row] = TAP_EMPTY; } } void NoteData::GetTracksHeldAtRow( int row, set& addTo ) { for( unsigned i=0; i viTracks; viTracks.clear(); GetTracksHeldAtRow( row, viTracks ); return viTracks.size(); }