#include "global.h" #include "NoteDataUtil.h" #include "NoteData.h" #include "RageUtil.h" #include "RageLog.h" #include "PlayerOptions.h" #include "song.h" #include "GameState.h" #include "RadarValues.h" #include "Foreach.h" #include NoteType NoteDataUtil::GetSmallestNoteTypeForMeasure( const NoteData &n, int iMeasureIndex ) { const int iMeasureStartIndex = iMeasureIndex * ROWS_PER_MEASURE; const int iMeasureLastIndex = (iMeasureIndex+1) * ROWS_PER_MEASURE - 1; // probe to find the smallest note type NoteType nt; for( nt=(NoteType)0; nt > aMeasureLines; for( unsigned m = 0; true; ++m ) { /* XXX Ignoring empty seems wrong for measures. It means that ",,," is treated as * "," where I would expect most people would want 2 empty measures. ",\n,\n," * would do as I would expect. */ split( sSMNoteData, ",", start, size, end, true ); // Ignore empty is important. if( start == end ) break; // Partial string split. int measureLineStart = start, measureLineSize = -1; const int measureEnd = start + size; aMeasureLines.clear(); while( true ) { // Ignore empty is clearly important here. split( sSMNoteData, "\n", measureLineStart, measureLineSize, measureEnd, true ); if( measureLineStart == measureEnd ) break; //RString &line = sSMNoteData.substr( measureLineStart, measureLineSize ); const char *beginLine = sSMNoteData.data() + measureLineStart; const char *endLine = beginLine + measureLineSize; while( beginLine < endLine && strchr("\r\n\t ", *beginLine) ) ++beginLine; while( endLine > beginLine && strchr("\r\n\t ", *(endLine - 1)) ) --endLine; if( beginLine < endLine ) // nonempty aMeasureLines.push_back( pair(beginLine, endLine) ); } for( unsigned l=0; lWarn( "Unmatched 3 in \"%.*s\"", n, beginLine ); } else { out.FindTapNote( iTrack, iHeadRow )->second.iDuration = iIndex - iHeadRow; } /* This won't write tn, but keep parsing normally anyway. */ break; } // case 'm': // Don't be loose with the definition. Use only 'M' since // that's what we've been writing to disk. -Chris case 'M': tn = TAP_ORIGINAL_MINE; break; // case 'A': tn = TAP_ORIGINAL_ATTACK; break; case 'K': tn = TAP_ORIGINAL_AUTO_KEYSOUND; break; case 'L': tn = TAP_ORIGINAL_LIFT; break; default: /* Invalid data. We don't want to assert, since there might * simply be invalid data in an .SM, and we don't want to die * due to invalid data. We should probably check for this when * we load SM data for the first time ... */ // ASSERT(0); tn = TAP_EMPTY; break; } p++; // We won't scan past the end of the line so these are safe to do. #if 0 // look for optional attack info (e.g. "{tipsy,50% drunk:15.2}") if( *p == '{' ) { p++; char szModifiers[256] = ""; float fDurationSeconds = 0; if( sscanf( p, "%255[^:]:%f}", szModifiers, &fDurationSeconds ) == 2 ) // not fatal if this fails due to malformed data { tn.type = TapNote::attack; tn.sAttackModifiers = szModifiers; tn.fAttackDurationSeconds = fDurationSeconds; } // skip past the '}' while( p < endLine ) { if( *(p++) == '}' ) break; } } #endif // look for optional keysound index (e.g. "[123]") if( *p == '[' ) { p++; int iKeysoundIndex = 0; if( 1 == sscanf( p, "%d]", &iKeysoundIndex ) ) // not fatal if this fails due to malformed data tn.iKeysoundIndex = iKeysoundIndex; // skip past the ']' while( p < endLine ) { if( *(p++) == ']' ) break; } } /* Optimization: if we pass TAP_EMPTY, NoteData will do a search * to remove anything in this position. We know that there's nothing * there, so avoid the search. */ if( tn.type != TapNote::empty && ch != '3' ) { tn.pn = pn; out.SetTapNote( iTrack, iIndex, tn ); } iTrack++; } } } /* Make sure we don't have any hold notes that didn't find a tail. */ for( int t=0; tsecond; if( tn.type == TapNote::hold_head && tn.iDuration == MAX_NOTE_ROW ) { int iRow = begin->first; LOG->UserLog( RageLog::LogType_General, "", "While loading SM note data, there was an unmatched 2 at beat %f", NoteRowToBeat(iRow) ); out.RemoveTapNote( t, begin ); } begin = next; } } } void NoteDataUtil::LoadFromSMNoteDataString( NoteData &out, const RString &sSMNoteData_, bool bComposite ) { // // Load note data // RString sSMNoteData; RString::size_type iIndexCommentStart = 0; RString::size_type iIndexCommentEnd = 0; RString::size_type origSize = sSMNoteData_.size(); const char *p = sSMNoteData_.data(); sSMNoteData.reserve( origSize ); while( (iIndexCommentStart = sSMNoteData_.find("//", iIndexCommentEnd)) != RString::npos ) { sSMNoteData.append( p, iIndexCommentStart - iIndexCommentEnd ); p += iIndexCommentStart - iIndexCommentEnd; iIndexCommentEnd = sSMNoteData_.find( "\n", iIndexCommentStart ); iIndexCommentEnd = (iIndexCommentEnd == RString::npos ? origSize : iIndexCommentEnd+1); p += iIndexCommentEnd - iIndexCommentStart; } sSMNoteData.append( p, origSize - iIndexCommentEnd ); /* Clear notes, but keep the same number of tracks. */ int iNumTracks = out.GetNumTracks(); out.Init(); out.SetNumTracks( iNumTracks ); if( !bComposite ) { LoadFromSMNoteDataStringWithPlayer( out, sSMNoteData, 0, sSMNoteData.size(), PLAYER_INVALID, iNumTracks ); return; } int start = 0, size = -1; vector vParts; FOREACH_PlayerNumber( pn ) { // Split in place. split( sSMNoteData, "&", start, size, false ); if( unsigned(start) == sSMNoteData.size() ) break; vParts.push_back( NoteData() ); NoteData &nd = vParts.back(); nd.SetNumTracks( iNumTracks ); LoadFromSMNoteDataStringWithPlayer( nd, sSMNoteData, start, size, pn, iNumTracks ); } CombineCompositeNoteData( out, vParts ); } void NoteDataUtil::InsertHoldTails( NoteData &inout ) { for( int t=0; t < inout.GetNumTracks(); t++ ) { NoteData::iterator begin = inout.begin(t), end = inout.end(t); for( ; begin != end; ++begin ) { int iRow = begin->first; const TapNote &tn = begin->second; if( tn.type != TapNote::hold_head ) continue; TapNote tail = tn; tail.type = TapNote::hold_tail; /* If iDuration is 0, we'd end up overwriting the head with the tail * (and invalidating our iterator). Empty hold notes aren't valid. */ ASSERT( tn.iDuration != 0 ); inout.SetTapNote( t, iRow + tn.iDuration, tail ); } } } void NoteDataUtil::GetSMNoteDataString( const NoteData &in, RString &sRet ) { // // Get note data // vector parts; float fLastBeat = -1.0f; SplitCompositeNoteData( in, parts ); FOREACH( NoteData, parts, nd ) { InsertHoldTails( *nd ); fLastBeat = max( fLastBeat, nd->GetLastBeat() ); } int iLastMeasure = int( fLastBeat/BEATS_PER_MEASURE ); sRet = ""; FOREACH( NoteData, parts, nd ) { if( nd != parts.begin() ) sRet.append( "&\n" ); for( int m = 0; m <= iLastMeasure; ++m ) // foreach measure { if( m ) sRet.append( 1, ',' ); sRet += ssprintf(" // measure %d\n", m+1); NoteType nt = GetSmallestNoteTypeForMeasure( *nd, m ); int iRowSpacing; if( nt == NOTE_TYPE_INVALID ) iRowSpacing = 1; else iRowSpacing = int(roundf( NoteTypeToBeat(nt) * ROWS_PER_BEAT )); // (verify first) // iRowSpacing = BeatToNoteRow( NoteTypeToBeat(nt) ); const int iMeasureStartRow = m * ROWS_PER_MEASURE; const int iMeasureLastRow = (m+1) * ROWS_PER_MEASURE - 1; for( int r=iMeasureStartRow; r<=iMeasureLastRow; r+=iRowSpacing ) { for( int t = 0; t < nd->GetNumTracks(); ++t ) { const TapNote &tn = nd->GetTapNote(t, r); char c; switch( tn.type ) { case TapNote::empty: c = '0'; break; case TapNote::tap: c = '1'; break; case TapNote::hold_head: switch( tn.subType ) { case TapNote::hold_head_hold: c = '2'; break; case TapNote::hold_head_roll: c = '4'; break; default: ASSERT(0); } break; case TapNote::hold_tail: c = '3'; break; case TapNote::mine: c = 'M'; break; case TapNote::attack: c = 'A'; break; case TapNote::autoKeysound: c = 'K'; break; case TapNote::lift: c = 'L'; break; default: FAIL_M( ssprintf("tn %i", tn.type) ); // invalid enum value } sRet.append( 1, c ); if( tn.type == TapNote::attack ) { sRet.append( ssprintf("{%s:%.2f}", tn.sAttackModifiers.c_str(), tn.fAttackDurationSeconds) ); } if( tn.iKeysoundIndex >= 0 ) sRet.append( ssprintf("[%d]",tn.iKeysoundIndex) ); } sRet.append( 1, '\n' ); } } } } void NoteDataUtil::SplitCompositeNoteData( const NoteData &in, vector &out ) { if( !in.IsComposite() ) { out.push_back( in ); return; } for( int t = 0; t < in.GetNumTracks(); ++t ) { for( NoteData::const_iterator iter = in.begin(t); iter != in.end(t); ++iter ) { int row = iter->first; TapNote tn = iter->second; unsigned index = int( tn.pn ); DEBUG_ASSERT( index < NUM_PlayerNumber ); while( out.size() <= index ) { out.push_back( NoteData() ); out.back().SetNumTracks( in.GetNumTracks() ); } tn.pn = PLAYER_INVALID; out[index].SetTapNote( t, row, tn ); } } } void NoteDataUtil::CombineCompositeNoteData( NoteData &out, const vector &in ) { FOREACH_CONST( NoteData, in, nd ) { const int iMaxTracks = min( out.GetNumTracks(), nd->GetNumTracks() ); for( int track = 0; track < iMaxTracks; ++track ) { for( NoteData::const_iterator i = nd->begin(track); i != nd->end(track); ++i ) { int row = i->first; if( out.IsHoldNoteAtRow(track, i->first) ) continue; if( i->second.type == TapNote::hold_head ) out.AddHoldNote( track, row, row + i->second.iDuration, i->second ); else out.SetTapNote( track, row, i->second ); } } } } void NoteDataUtil::LoadTransformedSlidingWindow( const NoteData &in, NoteData &out, int iNewNumTracks ) { // reset all notes out.Init(); if( in.GetNumTracks() > iNewNumTracks ) { /* Use a different algorithm for reducing tracks. */ LoadOverlapped( in, out, iNewNumTracks ); return; } out.SetNumTracks( iNewNumTracks ); if( in.GetNumTracks() == 0 ) return; // nothing to do and don't AV below int iCurTrackOffset = 0; int iTrackOffsetMin = 0; int iTrackOffsetMax = abs( iNewNumTracks - in.GetNumTracks() ); int bOffsetIncreasing = true; int iLastMeasure = 0; int iMeasuresSinceChange = 0; FOREACH_NONEMPTY_ROW_ALL_TRACKS( in, r ) { const int iMeasure = r / ROWS_PER_MEASURE; if( iMeasure != iLastMeasure ) ++iMeasuresSinceChange; if( iMeasure != iLastMeasure && iMeasuresSinceChange >= 4 ) // adjust sliding window every 4 measures at most { // See if there is a hold crossing the beginning of this measure bool bHoldCrossesThisMeasure = false; for( int t=0; t &aiTracks ) { for( unsigned i = 0; i < aiTracks.size(); ++i ) ASSERT_M( aiTracks[i] < out.GetNumTracks(), ssprintf("%i, %i", aiTracks[i], out.GetNumTracks()) ); FOREACH_NONEMPTY_ROW_ALL_TRACKS( in, r ) { /* If any row starts a hold note, find the end of the hold note, and keep searching * until we've extended to the end of the latest overlapping hold note. */ int iHoldStart = r; int iHoldEnd = -1; while(1) { int iMaxTailRow = FindLongestOverlappingHoldNoteForAnyTrack( in, r ); if( iMaxTailRow == -1 ) break; iHoldEnd = iMaxTailRow; r = iMaxTailRow; } if( iHoldEnd != -1 ) { /* If we found a hold note, add it to all tracks. */ for( unsigned i = 0; i < aiTracks.size(); ++i ) { int t = aiTracks[i]; out.AddHoldNote( t, iHoldStart, iHoldEnd, TAP_ORIGINAL_HOLD_HEAD ); } continue; } if( in.IsRowEmpty(r) ) continue; /* Enable every track in the output. */ for( unsigned i = 0; i < aiTracks.size(); ++i ) { int t = aiTracks[i]; out.SetTapNote( t, r, TAP_ORIGINAL_TAP ); } } } /* For every track enabled in "in", enable all tracks in "out". */ void NoteDataUtil::LoadTransformedLights( const NoteData &in, NoteData &out, int iNewNumTracks ) { // reset all notes out.Init(); out.SetNumTracks( iNewNumTracks ); vector aiTracks; for( int i = 0; i < out.GetNumTracks(); ++i ) aiTracks.push_back( i ); LightTransformHelper( in, out, aiTracks ); } /* This transform is specific to STEPS_TYPE_LIGHTS_CABINET. */ #include "LightsManager.h" // for LIGHT_* void NoteDataUtil::LoadTransformedLightsFromTwo( const NoteData &marquee, const NoteData &bass, NoteData &out ) { ASSERT( marquee.GetNumTracks() >= 4 ); ASSERT( bass.GetNumTracks() >= 1 ); /* For each track in "marquee", enable a track in the marquee lights. This will * reinit out. */ { NoteData transformed_marquee; transformed_marquee.CopyAll( marquee ); Wide( transformed_marquee ); const int iOriginalTrackToTakeFrom[NUM_CABINET_LIGHTS] = { 0, 1, 2, 3, -1, -1, -1, -1 }; out.LoadTransformed( transformed_marquee, NUM_CABINET_LIGHTS, iOriginalTrackToTakeFrom ); } /* For each track in "bass", enable the bass lights. */ { vector aiTracks; aiTracks.push_back( LIGHT_BASS_LEFT ); aiTracks.push_back( LIGHT_BASS_RIGHT ); LightTransformHelper( bass, out, aiTracks ); } /* Delete all mines. */ NoteDataUtil::RemoveMines( out ); } void NoteDataUtil::CalculateRadarValues( const NoteData &in, float fSongSeconds, RadarValues& out ) { // The for loop and the assert are used to ensure that all fields of // RadarValue get set in here. FOREACH_RadarCategory( rc ) { switch( rc ) { case RadarCategory_Stream: out[rc] = GetStreamRadarValue( in, fSongSeconds ); break; case RadarCategory_Voltage: out[rc] = GetVoltageRadarValue( in, fSongSeconds ); break; case RadarCategory_Air: out[rc] = GetAirRadarValue( in, fSongSeconds ); break; case RadarCategory_Freeze: out[rc] = GetFreezeRadarValue( in, fSongSeconds ); break; case RadarCategory_Chaos: out[rc] = GetChaosRadarValue( in, fSongSeconds ); break; case RadarCategory_TapsAndHolds: out[rc] = (float) in.GetNumRowsWithTapOrHoldHead(); break; case RadarCategory_Jumps: out[rc] = (float) in.GetNumJumps(); break; case RadarCategory_Holds: out[rc] = (float) in.GetNumHoldNotes(); break; case RadarCategory_Mines: out[rc] = (float) in.GetNumMines(); break; case RadarCategory_Hands: out[rc] = (float) in.GetNumHands(); break; case RadarCategory_Rolls: out[rc] = (float) in.GetNumRolls(); break; default: ASSERT(0); } } } float NoteDataUtil::GetStreamRadarValue( const NoteData &in, float fSongSeconds ) { if( !fSongSeconds ) return 0.0f; // density of steps int iNumNotes = in.GetNumTapNotes() + in.GetNumHoldNotes(); float fNotesPerSecond = iNumNotes/fSongSeconds; float fReturn = fNotesPerSecond / 7; return min( fReturn, 1.0f ); } float NoteDataUtil::GetVoltageRadarValue( const NoteData &in, float fSongSeconds ) { if( !fSongSeconds ) return 0.0f; const float fLastBeat = in.GetLastBeat(); const float fAvgBPS = fLastBeat / fSongSeconds; // peak density of steps float fMaxDensitySoFar = 0; const float BEAT_WINDOW = 8; const int BEAT_WINDOW_ROWS = BeatToNoteRow( BEAT_WINDOW ); for( int i=0; i<=BeatToNoteRow(fLastBeat); i+=BEAT_WINDOW_ROWS ) { int iNumNotesThisWindow = in.GetNumTapNotes( i, i+BEAT_WINDOW_ROWS ) + in.GetNumHoldNotes( i, i+BEAT_WINDOW_ROWS ); float fDensityThisWindow = iNumNotesThisWindow / BEAT_WINDOW; fMaxDensitySoFar = max( fMaxDensitySoFar, fDensityThisWindow ); } float fReturn = fMaxDensitySoFar*fAvgBPS/10; return min( fReturn, 1.0f ); } float NoteDataUtil::GetAirRadarValue( const NoteData &in, float fSongSeconds ) { if( !fSongSeconds ) return 0.0f; // number of doubles int iNumDoubles = in.GetNumJumps(); float fReturn = iNumDoubles / fSongSeconds; return min( fReturn, 1.0f ); } float NoteDataUtil::GetFreezeRadarValue( const NoteData &in, float fSongSeconds ) { if( !fSongSeconds ) return 0.0f; // number of hold steps float fReturn = in.GetNumHoldNotes() / fSongSeconds; return min( fReturn, 1.0f ); } float NoteDataUtil::GetChaosRadarValue( const NoteData &in, float fSongSeconds ) { if( !fSongSeconds ) return 0.0f; // count number of triplets or 16ths int iNumChaosNotes = 0; FOREACH_NONEMPTY_ROW_ALL_TRACKS( in, r ) { if( GetNoteType(r) >= NOTE_TYPE_12TH ) iNumChaosNotes++; } float fReturn = iNumChaosNotes / fSongSeconds * 0.5f; return min( fReturn, 1.0f ); } void NoteDataUtil::RemoveHoldNotes( NoteData &in, int iStartIndex, int iEndIndex ) { // turn all the HoldNotes into TapNotes for( int t=0; tsecond.type != TapNote::hold_head || begin->second.subType != TapNote::hold_head_hold ) continue; begin->second.type = TapNote::tap; } } } void NoteDataUtil::ChangeRollsToHolds( NoteData &in, int iStartIndex, int iEndIndex ) { for( int t=0; tsecond.type != TapNote::hold_head || begin->second.subType != TapNote::hold_head_roll ) continue; begin->second.subType = TapNote::hold_head_hold; } } } void NoteDataUtil::RemoveSimultaneousNotes( NoteData &in, int iMaxSimultaneous, int iStartIndex, int iEndIndex ) { // Remove tap and hold notes so no more than iMaxSimultaneous buttons are being held at any // given time. Never touch data outside of the range given; if many hold notes are overlapping // iStartIndex, and we'd have to change those holds to obey iMaxSimultaneous, just do the best // we can without doing so. if( in.IsComposite() ) { // Do this per part. vector vParts; SplitCompositeNoteData( in, vParts ); FOREACH( NoteData, vParts, nd ) RemoveSimultaneousNotes( *nd, iMaxSimultaneous, iStartIndex, iEndIndex ); in.Init(); in.SetNumTracks( vParts.front().GetNumTracks() ); CombineCompositeNoteData( in, vParts ); } FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( in, r, iStartIndex, iEndIndex ) { set viTracksHeld; in.GetTracksHeldAtRow( r, viTracksHeld ); // remove the first tap note or the first hold note that starts on this row int iTotalTracksPressed = in.GetNumTracksWithTapOrHoldHead(r) + viTracksHeld.size(); int iTracksToRemove = max( 0, iTotalTracksPressed - iMaxSimultaneous ); for( int t=0; iTracksToRemove>0 && tsecond.type == TapNote::tap ) inout.RemoveTapNote( track, iter ); } } void NoteDataUtil::RemoveAllButPlayer( NoteData &inout, PlayerNumber pn ) { for( int track = 0; track < inout.GetNumTracks(); ++track ) { NoteData::iterator i = inout.begin( track ); while( i != inout.end(track) ) { if( i->second.pn != pn && i->second.pn != PLAYER_INVALID ) inout.RemoveTapNote( track, i++ ); else ++i; } } } static void GetTrackMapping( StepsType st, NoteDataUtil::TrackMapping tt, int NumTracks, int *iTakeFromTrack ) { // Identity transform for cases not handled below. for( int t = 0; t < MAX_NOTE_TRACKS; ++t ) iTakeFromTrack[t] = t; switch( tt ) { case NoteDataUtil::left: case NoteDataUtil::right: // Is there a way to do this without handling each StepsType? -Chris switch( st ) { case STEPS_TYPE_DANCE_SINGLE: case STEPS_TYPE_DANCE_DOUBLE: case STEPS_TYPE_DANCE_COUPLE: case STEPS_TYPE_DANCE_ROUTINE: iTakeFromTrack[0] = 2; iTakeFromTrack[1] = 0; iTakeFromTrack[2] = 3; iTakeFromTrack[3] = 1; iTakeFromTrack[4] = 6; iTakeFromTrack[5] = 4; iTakeFromTrack[6] = 7; iTakeFromTrack[7] = 5; break; case STEPS_TYPE_DANCE_SOLO: iTakeFromTrack[0] = 5; iTakeFromTrack[1] = 4; iTakeFromTrack[2] = 0; iTakeFromTrack[3] = 3; iTakeFromTrack[4] = 1; iTakeFromTrack[5] = 2; break; case STEPS_TYPE_PUMP_SINGLE: case STEPS_TYPE_PUMP_COUPLE: iTakeFromTrack[0] = 3; iTakeFromTrack[1] = 4; iTakeFromTrack[2] = 2; iTakeFromTrack[3] = 0; iTakeFromTrack[4] = 1; iTakeFromTrack[5] = 8; iTakeFromTrack[6] = 9; iTakeFromTrack[7] = 7; iTakeFromTrack[8] = 5; iTakeFromTrack[9] = 6; break; case STEPS_TYPE_PUMP_HALFDOUBLE: iTakeFromTrack[0] = 2; iTakeFromTrack[1] = 0; iTakeFromTrack[2] = 1; iTakeFromTrack[3] = 3; iTakeFromTrack[4] = 4; iTakeFromTrack[5] = 5; break; case STEPS_TYPE_PUMP_DOUBLE: iTakeFromTrack[0] = 8; iTakeFromTrack[1] = 9; iTakeFromTrack[2] = 7; iTakeFromTrack[3] = 5; iTakeFromTrack[4] = 6; iTakeFromTrack[5] = 3; iTakeFromTrack[6] = 4; iTakeFromTrack[7] = 2; iTakeFromTrack[8] = 0; iTakeFromTrack[9] = 1; break; default: break; } if( tt == NoteDataUtil::right ) { /* Invert. */ int iTrack[MAX_NOTE_TRACKS]; memcpy( iTrack, iTakeFromTrack, sizeof(iTrack) ); for( int t = 0; t < MAX_NOTE_TRACKS; ++t ) { const int to = iTrack[t]; iTakeFromTrack[to] = t; } } break; case NoteDataUtil::mirror: for( int t=0; tm_iStageSeed; do { RandomGen rnd( iShuffleSeed ); random_shuffle( &iTakeFromTrack[0], &iTakeFromTrack[NumTracks], rnd ); iShuffleSeed++; } while ( !memcmp( iOrig, iTakeFromTrack, sizeof(iOrig) ) ); } break; case NoteDataUtil::stomp: switch( st ) { case STEPS_TYPE_DANCE_SINGLE: case STEPS_TYPE_DANCE_COUPLE: iTakeFromTrack[0] = 3; iTakeFromTrack[1] = 2; iTakeFromTrack[2] = 1; iTakeFromTrack[3] = 0; iTakeFromTrack[4] = 7; iTakeFromTrack[5] = 6; iTakeFromTrack[6] = 5; iTakeFromTrack[7] = 4; break; case STEPS_TYPE_DANCE_DOUBLE: case STEPS_TYPE_DANCE_ROUTINE: iTakeFromTrack[0] = 1; iTakeFromTrack[1] = 0; iTakeFromTrack[2] = 3; iTakeFromTrack[3] = 2; iTakeFromTrack[4] = 5; iTakeFromTrack[5] = 4; iTakeFromTrack[6] = 7; iTakeFromTrack[7] = 6; break; default: break; } break; default: ASSERT(0); } } static void SuperShuffleTaps( NoteData &inout, int iStartIndex, int iEndIndex ) { /* * We already did the normal shuffling code above, which did a good job * of shuffling HoldNotes without creating impossible patterns. * Now, go in and shuffle the TapNotes per-row. * * This is only called by NoteDataUtil::Turn. */ FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( inout, r, iStartIndex, iEndIndex ) { for( int t1=0; t1 vTriedTracks; for( int i=0; i<4; i++ ) // probe max 4 times { int t2 = RandomInt( inout.GetNumTracks() ); if( vTriedTracks.find(t2) != vTriedTracks.end() ) // already tried this track continue; // skip vTriedTracks.insert( t2 ); // swapping with ourself is a no-op if( t1 == t2 ) break; // done swapping const TapNote &tn2 = inout.GetTapNote( t2, r ); switch( tn2.type ) { case TapNote::hold_head: case TapNote::hold_tail: case TapNote::autoKeysound: continue; // don't swap with these case TapNote::empty: case TapNote::tap: case TapNote::mine: case TapNote::attack: case TapNote::lift: break; // ok to swap with this DEFAULT_FAIL( tn2.type ); } // don't swap into the middle of a hold note if( inout.IsHoldNoteAtRow( t2,r ) ) continue; // do the swap const TapNote tnTemp = tn1; inout.SetTapNote( t1, r, tn2 ); inout.SetTapNote( t2, r, tnTemp ); break; // done swapping } } } } void NoteDataUtil::Turn( NoteData &inout, StepsType st, TrackMapping tt, int iStartIndex, int iEndIndex ) { int iTakeFromTrack[MAX_NOTE_TRACKS]; // New track "t" will take from old track iTakeFromTrack[t] GetTrackMapping( st, tt, inout.GetNumTracks(), iTakeFromTrack ); NoteData tempNoteData; tempNoteData.LoadTransformed( inout, inout.GetNumTracks(), iTakeFromTrack ); if( tt == super_shuffle ) SuperShuffleTaps( tempNoteData, iStartIndex, iEndIndex ); inout.CopyAll( tempNoteData ); } void NoteDataUtil::Backwards( NoteData &inout ) { NoteData out; out.SetNumTracks( inout.GetNumTracks() ); int max_row = inout.GetLastRow(); for( int t=0; t 0 ) { bSpaceAroundIsEmpty = false; break; } if( !bSpaceAroundIsEmpty ) continue; // skip // add a note determinitsitcally int iBeat = (int)roundf( NoteRowToBeat(i) ); int iTrackOfNote = inout.GetFirstTrackWithTap(i); int iTrackToAdd = iTrackOfNote + (iBeat%5)-2; // won't be more than 2 tracks away from the existing note CLAMP( iTrackToAdd, 0, inout.GetNumTracks()-1 ); if( iTrackToAdd == iTrackOfNote ) iTrackToAdd++; CLAMP( iTrackToAdd, 0, inout.GetNumTracks()-1 ); if( iTrackToAdd == iTrackOfNote ) iTrackToAdd--; CLAMP( iTrackToAdd, 0, inout.GetNumTracks()-1 ); if( inout.GetTapNote(iTrackToAdd, i).type != TapNote::empty ) { iTrackToAdd = (iTrackToAdd+1) % inout.GetNumTracks(); } inout.SetTapNote(iTrackToAdd, i, TAP_ADDITION_TAP); } } void NoteDataUtil::Big( NoteData &inout, int iStartIndex, int iEndIndex ) { // add 8ths between 4ths InsertIntelligentTaps( inout,BeatToNoteRow(1.0f), BeatToNoteRow(0.5f), BeatToNoteRow(1.0f), false,iStartIndex,iEndIndex ); } void NoteDataUtil::Quick( NoteData &inout, int iStartIndex, int iEndIndex ) { // add 16ths between 8ths InsertIntelligentTaps( inout, BeatToNoteRow(0.5f), BeatToNoteRow(0.25f), BeatToNoteRow(1.0f), false,iStartIndex,iEndIndex ); } // Due to popular request by people annoyed with the "new" implementation of Quick, we now have // this BMR-izer for your steps. Use with caution. void NoteDataUtil::BMRize( NoteData &inout, int iStartIndex, int iEndIndex ) { Big( inout, iStartIndex, iEndIndex ); Quick( inout, iStartIndex, iEndIndex ); } void NoteDataUtil::Skippy( NoteData &inout, int iStartIndex, int iEndIndex ) { // add 16ths between 4ths InsertIntelligentTaps( inout, BeatToNoteRow(1.0f), BeatToNoteRow(0.75f),BeatToNoteRow(1.0f), true,iStartIndex,iEndIndex ); } void NoteDataUtil::InsertIntelligentTaps( NoteData &inout, int iWindowSizeRows, int iInsertOffsetRows, int iWindowStrideRows, bool bSkippy, int iStartIndex, int iEndIndex ) { ASSERT( iInsertOffsetRows <= iWindowSizeRows ); ASSERT( iWindowSizeRows <= iWindowStrideRows ); bool bRequireNoteAtBeginningOfWindow = !bSkippy; bool bRequireNoteAtEndOfWindow = true; /* Start on a multiple of fBeatInterval. */ iStartIndex = Quantize( iStartIndex, iWindowStrideRows ); FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( inout, i, iStartIndex, iEndIndex ) { // Insert a beat in the middle of every fBeatInterval. if( i % iWindowStrideRows != 0 ) continue; // even beats only int iRowEarlier = i; int iRowLater = i + iWindowSizeRows; int iRowToAdd = i + iInsertOffsetRows; // following two lines have been changed because the behavior of treating hold-heads // as different from taps doesn't feel right, and because we need to check // against TAP_ADDITION with the BMRize mod. if( bRequireNoteAtBeginningOfWindow ) if( inout.GetNumTapNonEmptyTracks(iRowEarlier)!=1 || inout.GetNumTracksWithTapOrHoldHead(iRowEarlier)!=1 ) continue; if( bRequireNoteAtEndOfWindow ) if( inout.GetNumTapNonEmptyTracks(iRowLater)!=1 || inout.GetNumTracksWithTapOrHoldHead(iRowLater)!=1 ) continue; // there is a 4th and 8th note surrounding iRowBetween // don't insert a new note if there's already one within this interval bool bNoteInMiddle = false; for( int t = 0; t < inout.GetNumTracks(); ++t ) if( inout.IsHoldNoteAtRow(t, iRowEarlier+1) ) bNoteInMiddle = true; FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( inout, j, iRowEarlier+1, iRowLater-1 ) bNoteInMiddle = true; if( bNoteInMiddle ) continue; // add a note deterministically somewhere on a track different from the two surrounding notes int iTrackOfNoteEarlier = -1; bool bEarlierHasNonEmptyTrack = inout.GetTapFirstNonEmptyTrack( iRowEarlier, iTrackOfNoteEarlier ); int iTrackOfNoteLater = -1; inout.GetTapFirstNonEmptyTrack( iRowLater, iTrackOfNoteLater ); int iTrackOfNoteToAdd = 0; if( bSkippy && iTrackOfNoteEarlier != iTrackOfNoteLater ) // Don't make skips on the same note { if( bEarlierHasNonEmptyTrack ) { iTrackOfNoteToAdd = iTrackOfNoteEarlier; goto done_looking_for_track_to_add; } } // try to choose a track between the earlier and later notes if( abs(iTrackOfNoteEarlier-iTrackOfNoteLater) >= 2 ) { iTrackOfNoteToAdd = min(iTrackOfNoteEarlier,iTrackOfNoteLater)+1; goto done_looking_for_track_to_add; } // try to choose a track just to the left if( min(iTrackOfNoteEarlier,iTrackOfNoteLater)-1 >= 0 ) { iTrackOfNoteToAdd = min(iTrackOfNoteEarlier,iTrackOfNoteLater)-1; goto done_looking_for_track_to_add; } // try to choose a track just to the right if( max(iTrackOfNoteEarlier,iTrackOfNoteLater)+1 < inout.GetNumTracks() ) { iTrackOfNoteToAdd = max(iTrackOfNoteEarlier,iTrackOfNoteLater)+1; goto done_looking_for_track_to_add; } done_looking_for_track_to_add: inout.SetTapNote(iTrackOfNoteToAdd, iRowToAdd, TAP_ADDITION_TAP); } } #if 0 class TrackIterator { public: TrackIterator(); /* If called, iterate only over [iStart,iEnd]. */ void SetRange( int iStart, int iEnd ) { } /* If called, pay attention to iTrack only. */ void SetTrack( iTrack ); /* Extend iStart and iEnd to include hold notes overlapping the boundaries. Call SetRange() * and SetTrack() first. */ void HoldInclusive(); /* Reduce iStart and iEnd to exclude hold notes overlapping the boundaries. Call SetRange() * and SetTrack() first. */ void HoldExclusive(); /* If called, keep the iterator around. This results in much faster iteration. If used, * ensure that the current row will always remain valid. SetTrack() must be called first. */ void Fast(); /* Retrieve an iterator for the current row. SetTrack() must be called first (but Fast() * does not). */ TapNote::iterator Get(); int GetRow() const { return m_iCurrentRow; } bool Prev(); bool Next(); private: int m_iStart, m_iEnd; int m_iTrack; bool m_bFast; int m_iCurrentRow; NoteData::iterator m_Iterator; /* m_bFast only: */ NoteData::iterator m_Begin, m_End; }; bool TrackIterator::Next() { if( m_bFast ) { if( m_Iterator == } } TrackIterator::TrackIterator() { m_iStart = 0; m_iEnd = MAX_NOTE_ROW; m_iTrack = -1; } #endif void NoteDataUtil::AddMines( NoteData &inout, int iStartIndex, int iEndIndex ) { // // Change whole rows at a time to be tap notes. Otherwise, it causes // major problems for our scoring system. -Chris // int iRowCount = 0; int iPlaceEveryRows = 6; FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( inout, r, iStartIndex, iEndIndex ) { iRowCount++; // place every 6 or 7 rows // XXX: What is "6 or 7" derived from? Can we calculate that in a way // that won't break if ROWS_PER_MEASURE changes? if( iRowCount>=iPlaceEveryRows ) { for( int t=0; t iEndIndex ) continue; // Only place a mines if there's not another step nearby int iMineRangeBegin = iMineRow - BeatToNoteRow( 0.5f ) + 1; int iMineRangeEnd = iMineRow + BeatToNoteRow( 0.5f ) - 1; if( !inout.IsRangeEmpty(iTrack, iMineRangeBegin, iMineRangeEnd) ) continue; // Add a mine right after the hold end. inout.SetTapNote( iTrack, iMineRow, TAP_ADDITION_MINE ); // Convert all notes in this row to mines. for( int t=0; t viTracks; inout.GetTracksHeldAtRow( iRowEcho, viTracks ); // don't lay if holding 2 already if( viTracks.size() >= 2 ) continue; // don't lay // don't lay echos on top of a HoldNote if( find(viTracks.begin(),viTracks.end(),iEchoTrack) != viTracks.end() ) continue; // don't lay } inout.SetTapNote( iEchoTrack, iRowEcho, TAP_ADDITION_TAP ); } } void NoteDataUtil::Planted( NoteData &inout, int iStartIndex, int iEndIndex ) { ConvertTapsToHolds( inout, 1, iStartIndex, iEndIndex ); } void NoteDataUtil::Floored( NoteData &inout, int iStartIndex, int iEndIndex ) { ConvertTapsToHolds( inout, 2, iStartIndex, iEndIndex ); } void NoteDataUtil::Twister( NoteData &inout, int iStartIndex, int iEndIndex ) { ConvertTapsToHolds( inout, 3, iStartIndex, iEndIndex ); } void NoteDataUtil::ConvertTapsToHolds( NoteData &inout, int iSimultaneousHolds, int iStartIndex, int iEndIndex ) { // Convert all taps to freezes. FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( inout, r, iStartIndex, iEndIndex ) { int iTrackAddedThisRow = 0; for( int t=0; t iSimultaneousHolds ) break; if( inout.GetTapNote(t,r).type == TapNote::tap ) { // Find the ending row for this hold int iTapsLeft = iSimultaneousHolds; int r2 = r+1; FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( inout, next_row, r+1, iEndIndex ) { r2 = next_row; // If there are two taps in a row on the same track, // don't convert the earlier one to a hold. if( inout.GetTapNote(t,r2).type != TapNote::empty ) goto dont_add_hold; set tracksDown; inout.GetTracksHeldAtRow( r2, tracksDown ); inout.GetTapNonEmptyTracks( r2, tracksDown ); iTapsLeft -= tracksDown.size(); if( iTapsLeft == 0 ) break; // we found the ending row for this hold else if( iTapsLeft < 0 ) goto dont_add_hold; } // If the steps end in a tap, convert that tap // to a hold that lasts for at least one beat. if( r2 == r+1 ) r2 = r+BeatToNoteRow(1); inout.AddHoldNote( t, r, r2, TAP_ORIGINAL_HOLD_HEAD ); iTrackAddedThisRow++; } dont_add_hold: ; } } } void NoteDataUtil::Stomp( NoteData &inout, StepsType st, int iStartIndex, int iEndIndex ) { // Make all non jumps with ample space around them into jumps. int iTrackMapping[MAX_NOTE_TRACKS]; GetTrackMapping( st, stomp, inout.GetNumTracks(), iTrackMapping ); FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( inout, r, iStartIndex, iEndIndex ) { if( inout.GetNumTracksWithTap(r) != 1 ) continue; // skip for( int t=0; t= 1 ) continue; int iOppositeTrack = iTrackMapping[t]; inout.SetTapNote( iOppositeTrack, r, TAP_ADDITION_TAP ); } } } } void NoteDataUtil::SnapToNearestNoteType( NoteData &inout, NoteType nt1, NoteType nt2, int iStartIndex, int iEndIndex ) { // nt2 is optional and should be NOTE_TYPE_INVALID if it is not used float fSnapInterval1 = NoteTypeToBeat( nt1 ); float fSnapInterval2 = 10000; // nothing will ever snap to this. That's what we want! if( nt2 != NOTE_TYPE_INVALID ) fSnapInterval2 = NoteTypeToBeat( nt2 ); // iterate over all TapNotes in the interval and snap them FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( inout, iOldIndex, iStartIndex, iEndIndex ) { int iNewIndex1 = Quantize( iOldIndex, BeatToNoteRow(fSnapInterval1) ); int iNewIndex2 = Quantize( iOldIndex, BeatToNoteRow(fSnapInterval2) ); bool bNewBeat1IsCloser = abs(iNewIndex1-iOldIndex) < abs(iNewIndex2-iOldIndex); int iNewIndex = bNewBeat1IsCloser? iNewIndex1 : iNewIndex2; for( int c=0; csecond ); inout.RemoveTapNote( t, iter ); } } void NoteDataUtil::CollapseLeft( NoteData &inout ) { FOREACH_NONEMPTY_ROW_ALL_TRACKS( inout, r ) { int iNumTracksFilled = 0; for( int t=0; t vpValidRowsToCheck; for( unsigned i=0; isModifiers ); if( po.ContainsTransformOrTurn() ) { float fStartBeat, fEndBeat; a->GetAttackBeats( pSong, fStartBeat, fEndBeat ); NoteDataUtil::TransformNoteData( nd, po, st, BeatToNoteRow(fStartBeat), BeatToNoteRow(fEndBeat) ); } } } void NoteDataUtil::TransformNoteData( NoteData &nd, const PlayerOptions &po, StepsType st, int iStartIndex, int iEndIndex ) { // Apply remove transforms before others so that we don't go removing // notes we just inserted. Apply TRANSFORM_NOROLLS before TRANSFORM_NOHOLDS, // since NOROLLS creates holds. if( po.m_bTransforms[PlayerOptions::TRANSFORM_LITTLE] ) NoteDataUtil::Little( nd, iStartIndex, iEndIndex ); if( po.m_bTransforms[PlayerOptions::TRANSFORM_NOROLLS] ) NoteDataUtil::ChangeRollsToHolds( nd, iStartIndex, iEndIndex ); if( po.m_bTransforms[PlayerOptions::TRANSFORM_NOHOLDS] ) NoteDataUtil::RemoveHoldNotes( nd, iStartIndex, iEndIndex ); if( po.m_bTransforms[PlayerOptions::TRANSFORM_NOMINES] ) NoteDataUtil::RemoveMines( nd, iStartIndex, iEndIndex ); if( po.m_bTransforms[PlayerOptions::TRANSFORM_NOJUMPS] ) NoteDataUtil::RemoveJumps( nd, iStartIndex, iEndIndex ); if( po.m_bTransforms[PlayerOptions::TRANSFORM_NOHANDS] ) NoteDataUtil::RemoveHands( nd, iStartIndex, iEndIndex ); if( po.m_bTransforms[PlayerOptions::TRANSFORM_NOQUADS] ) NoteDataUtil::RemoveQuads( nd, iStartIndex, iEndIndex ); if( po.m_bTransforms[PlayerOptions::TRANSFORM_NOSTRETCH] ) NoteDataUtil::RemoveStretch( nd, st, iStartIndex, iEndIndex ); // Apply inserts. if( po.m_bTransforms[PlayerOptions::TRANSFORM_BIG] ) NoteDataUtil::Big( nd, iStartIndex, iEndIndex ); if( po.m_bTransforms[PlayerOptions::TRANSFORM_QUICK] ) NoteDataUtil::Quick( nd, iStartIndex, iEndIndex ); if( po.m_bTransforms[PlayerOptions::TRANSFORM_BMRIZE] ) NoteDataUtil::BMRize( nd, iStartIndex, iEndIndex ); // Skippy will still add taps to places that the other // AddIntelligentTaps above won't. if( po.m_bTransforms[PlayerOptions::TRANSFORM_SKIPPY] ) NoteDataUtil::Skippy( nd, iStartIndex, iEndIndex ); // These aren't affects by the above inserts very much. if( po.m_bTransforms[PlayerOptions::TRANSFORM_MINES] ) NoteDataUtil::AddMines( nd, iStartIndex, iEndIndex ); if( po.m_bTransforms[PlayerOptions::TRANSFORM_ECHO] ) NoteDataUtil::Echo( nd, iStartIndex, iEndIndex ); // Jump-adding transforms aren't much affected by additional taps. if( po.m_bTransforms[PlayerOptions::TRANSFORM_WIDE] ) NoteDataUtil::Wide( nd, iStartIndex, iEndIndex ); if( po.m_bTransforms[PlayerOptions::TRANSFORM_STOMP] ) NoteDataUtil::Stomp( nd, st, iStartIndex, iEndIndex ); // Transforms that add holds go last. If they went first, most tap-adding // transforms wouldn't do anything because tap-adding transforms skip areas // where there's a hold. if( po.m_bTransforms[PlayerOptions::TRANSFORM_PLANTED] ) NoteDataUtil::Planted( nd, iStartIndex, iEndIndex ); if( po.m_bTransforms[PlayerOptions::TRANSFORM_FLOORED] ) NoteDataUtil::Floored( nd, iStartIndex, iEndIndex ); if( po.m_bTransforms[PlayerOptions::TRANSFORM_TWISTER] ) NoteDataUtil::Twister( nd, iStartIndex, iEndIndex ); // Apply turns and shuffles last to that they affect inserts. if( po.m_bTurns[PlayerOptions::TURN_MIRROR] ) NoteDataUtil::Turn( nd, st, NoteDataUtil::mirror, iStartIndex, iEndIndex ); if( po.m_bTurns[PlayerOptions::TURN_LEFT] ) NoteDataUtil::Turn( nd, st, NoteDataUtil::left, iStartIndex, iEndIndex ); if( po.m_bTurns[PlayerOptions::TURN_RIGHT] ) NoteDataUtil::Turn( nd, st, NoteDataUtil::right, iStartIndex, iEndIndex ); if( po.m_bTurns[PlayerOptions::TURN_SHUFFLE] ) NoteDataUtil::Turn( nd, st, NoteDataUtil::shuffle, iStartIndex, iEndIndex ); if( po.m_bTurns[PlayerOptions::TURN_SUPER_SHUFFLE] ) NoteDataUtil::Turn( nd, st, NoteDataUtil::super_shuffle, iStartIndex, iEndIndex ); } void NoteDataUtil::AddTapAttacks( NoteData &nd, Song* pSong ) { // throw an attack in every 30 seconds const char* szAttacks[3] = { "2x", "drunk", "dizzy", }; for( float sec=15; secm_fMusicLengthSeconds; sec+=30 ) { float fBeat = pSong->GetBeatFromElapsedTime( sec ); int iBeat = (int)fBeat; int iTrack = iBeat % nd.GetNumTracks(); // deterministically calculates track TapNote tn( TapNote::attack, TapNote::SubType_invalid, TapNote::original, szAttacks[RandomInt(ARRAYLEN(szAttacks))], 15.0f, -1 ); nd.SetTapNote( iTrack, BeatToNoteRow(fBeat), tn ); } } #if 0 // undo this if ScaleRegion breaks more things than it fixes void NoteDataUtil::Scale( NoteData &nd, float fScale ) { ASSERT( fScale > 0 ); NoteData temp; temp.CopyAll( &nd ); nd.ClearAll(); for( int r=0; r<=temp.GetLastRow(); r++ ) { for( int t=0; t 0 ); ASSERT( iStartIndex < iEndIndex ); ASSERT( iStartIndex >= 0 ); NoteData temp1, temp2; temp1.SetNumTracks( nd.GetNumTracks() ); temp2.SetNumTracks( nd.GetNumTracks() ); if( iStartIndex != 0 ) temp1.CopyRange( nd, 0, iStartIndex ); if( iEndIndex != MAX_NOTE_ROW ) { const int iScaledFirstRowAfterRegion = int(iStartIndex + (iEndIndex - iStartIndex) * fScale); temp1.CopyRange( nd, iEndIndex, MAX_NOTE_ROW, iScaledFirstRowAfterRegion ); } temp2.CopyRange( nd, iStartIndex, iEndIndex ); nd.ClearAll(); for( int t=0; tfirst*fScale + iStartIndex ); temp1.SetTapNote( t, new_row, iter->second ); } } nd.swap( temp1 ); } void NoteDataUtil::InsertRows( NoteData &nd, int iStartIndex, int iRowsToAdd ) { ASSERT( iRowsToAdd >= 0 ); NoteData temp; temp.SetNumTracks( nd.GetNumTracks() ); temp.CopyRange( nd, iStartIndex, MAX_NOTE_ROW ); nd.ClearRange( iStartIndex, MAX_NOTE_ROW ); nd.CopyRange( temp, 0, MAX_NOTE_ROW, iStartIndex + iRowsToAdd ); } void NoteDataUtil::DeleteRows( NoteData &nd, int iStartIndex, int iRowsToDelete ) { ASSERT( iRowsToDelete >= 0 ); NoteData temp; temp.SetNumTracks( nd.GetNumTracks() ); temp.CopyRange( nd, iStartIndex + iRowsToDelete, MAX_NOTE_ROW ); nd.ClearRange( iStartIndex, MAX_NOTE_ROW ); nd.CopyRange( temp, 0, MAX_NOTE_ROW, iStartIndex ); } void NoteDataUtil::RemoveAllTapsOfType( NoteData& ndInOut, TapNote::Type typeToRemove ) { /* Be very careful when deleting the tap notes. Erasing elements from maps using * iterators invalidates only the iterator that is being erased. To that end, * increment the iterator before deleting the elment of the map. */ for( int t=0; tsecond.type == typeToRemove ) ndInOut.RemoveTapNote( t, iter++ ); else ++iter; } } } void NoteDataUtil::RemoveAllTapsExceptForType( NoteData& ndInOut, TapNote::Type typeToKeep ) { /* Same as in RemoveAllTapsOfType(). */ for( int t=0; tsecond.type != typeToKeep ) ndInOut.RemoveTapNote( t, iter++ ); else ++iter; } } } int NoteDataUtil::GetNumUsedTracks( const NoteData& in ) { for( int t=0; t= iEnd ) return false; // for each index we crossed since the last update: FOREACH_NONEMPTY_ROW_IN_TRACK_RANGE( in, iTrack, r, iStart, iEnd ) { switch( in.GetTapNote( iTrack, r ).type ) { case TapNote::empty: case TapNote::mine: continue; default: return true; } } if( in.IsHoldNoteAtRow( iTrack, iEnd ) ) return true; return false; } /* Find the next row that either starts a TapNote, or ends a previous one. */ bool NoteDataUtil::GetNextEditorPosition( const NoteData& in, int &rowInOut ) { int iOriginalRow = rowInOut; bool bAnyHaveNextNote = in.GetNextTapNoteRowForAllTracks( rowInOut ); int iClosestNextRow = rowInOut; if( !bAnyHaveNextNote ) iClosestNextRow = MAX_NOTE_ROW; for( int t=0; t= iOriginalRow ) continue; bAnyHavePrevNote = true; ASSERT( iEndRow < MAX_NOTE_ROW ); iClosestPrevRow = max( iClosestPrevRow, iEndRow ); } if( !bAnyHavePrevNote ) return false; rowInOut = iClosestPrevRow; return true; } /* * (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. */