2011-03-17 01:47:30 -04:00
#include "global.h"
#include "NoteDataUtil.h"
#include "NoteData.h"
#include "RageUtil.h"
#include "RageLog.h"
#include "PlayerOptions.h"
#include "Song.h"
#include "Style.h"
#include "GameState.h"
#include "RadarValues.h"
#include "Foreach.h"
2013-12-19 23:46:01 -06:00
#include "TimingData.h"
2011-03-17 01:47:30 -04:00
#include <utility>
// TODO: Remove these constants that aren't time signature-aware
static const int BEATS_PER_MEASURE = 4 ;
static const int ROWS_PER_MEASURE = ROWS_PER_BEAT * BEATS_PER_MEASURE ;
NoteType NoteDataUtil :: GetSmallestNoteTypeForMeasure ( const NoteData & nd , int iMeasureIndex )
{
const int iMeasureStartIndex = iMeasureIndex * ROWS_PER_MEASURE ;
const int iMeasureEndIndex = ( iMeasureIndex + 1 ) * ROWS_PER_MEASURE ;
return NoteDataUtil :: GetSmallestNoteTypeInRange ( nd , iMeasureStartIndex , iMeasureEndIndex );
}
NoteType NoteDataUtil :: GetSmallestNoteTypeInRange ( const NoteData & n , int iStartIndex , int iEndIndex )
{
// probe to find the smallest note type
2011-09-10 21:33:00 -04:00
FOREACH_ENUM ( NoteType , nt )
2011-03-17 01:47:30 -04:00
{
float fBeatSpacing = NoteTypeToBeat ( nt );
int iRowSpacing = lrintf ( fBeatSpacing * ROWS_PER_BEAT );
bool bFoundSmallerNote = false ;
// for each index in this measure
FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE ( n , i , iStartIndex , iEndIndex )
{
if ( i % iRowSpacing == 0 )
continue ; // skip
if ( ! n . IsRowEmpty ( i ) )
{
bFoundSmallerNote = true ;
break ;
}
}
if ( bFoundSmallerNote )
continue ; // searching the next NoteType
else
2011-09-10 21:33:00 -04:00
return nt ; // stop searching. We found the smallest NoteType
2011-03-17 01:47:30 -04:00
}
2011-09-10 21:33:00 -04:00
return NoteType_Invalid ; // well-formed notes created in the editor should never get here
2011-03-17 01:47:30 -04:00
}
static void LoadFromSMNoteDataStringWithPlayer ( NoteData & out , const RString & sSMNoteData , int start ,
int len , PlayerNumber pn , int iNumTracks )
{
/* Don't allocate memory for the entire string, nor per measure. Instead, use the in-place
* partial string split twice. By maintaining begin and end pointers to each measure line
* we can perform this without copying the string at all. */
int size = - 1 ;
const int end = start + len ;
vector < pair < const char * , const char *> > 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 < const char * , const char *> ( beginLine , endLine ) );
}
for ( unsigned l = 0 ; l < aMeasureLines . size (); l ++ )
{
const char * p = aMeasureLines [ l ]. first ;
const char * const beginLine = p ;
const char * const endLine = aMeasureLines [ l ]. second ;
const float fPercentIntoMeasure = l / ( float ) aMeasureLines . size ();
const float fBeat = ( m + fPercentIntoMeasure ) * BEATS_PER_MEASURE ;
const int iIndex = BeatToNoteRow ( fBeat );
int iTrack = 0 ;
while ( iTrack < iNumTracks && p < endLine )
{
TapNote tn ;
char ch = * p ;
switch ( ch )
{
case '0' : tn = TAP_EMPTY ; break ;
case '1' : tn = TAP_ORIGINAL_TAP ; break ;
case '2' :
case '4' :
// case 'N': // minefield
tn = ch == '2' ? TAP_ORIGINAL_HOLD_HEAD : TAP_ORIGINAL_ROLL_HEAD ;
/*
// upcoming code for minefields -aj
switch(ch)
{
case '2': tn = TAP_ORIGINAL_HOLD_HEAD; break;
case '4': tn = TAP_ORIGINAL_ROLL_HEAD; break;
case 'N': tn = TAP_ORIGINAL_MINE_HEAD; break;
}
*/
/* Set the hold note to have infinite length. We'll clamp
* it when we hit the tail. */
tn . iDuration = MAX_NOTE_ROW ;
break ;
case '3' :
{
// This is the end of a hold. Search for the beginning.
int iHeadRow ;
if ( ! out . IsHoldNoteAtRow ( iTrack , iIndex , & iHeadRow ) )
{
int n = intptr_t ( endLine ) - intptr_t ( beginLine );
LOG -> Warn ( "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 ;
case 'F' : tn = TAP_ORIGINAL_FAKE ; break ;
// case 'I': tn = TAP_ORIGINAL_ITEM; 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 ... */
2012-12-27 16:59:35 -05:00
// FAIL_M("Invalid data in SM");
2011-03-17 01:47:30 -04:00
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
{
2014-07-25 18:45:02 -05:00
tn.type = TapNoteType_Attack;
2011-03-17 01:47:30 -04:00
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 ;
}
}
#if 0
// look for optional item name (e.g. "<potion>"),
// where the name in the <> is a Lua function defined elsewhere
// (Data/ItemTypes.lua, perhaps?) -aj
if( *p == '<' )
{
p++;
// skip past the '>'
while( p < endLine )
{
if( *(p++) == '>' )
break;
}
}
#endif
/* 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. */
2014-07-25 18:45:02 -05:00
if ( tn . type != TapNoteType_Empty && ch != '3' )
2011-03-17 01:47:30 -04:00
{
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 ; t < out . GetNumTracks (); t ++ )
{
NoteData :: iterator begin = out . begin ( t );
NoteData :: iterator lEnd = out . end ( t );
while ( begin != lEnd )
{
NoteData :: iterator next = Increment ( begin );
const TapNote & tn = begin -> second ;
2014-07-25 18:45:02 -05:00
if ( tn . type == TapNoteType_HoldHead && tn . iDuration == MAX_NOTE_ROW )
2011-03-17 01:47:30 -04:00
{
int iRow = begin -> first ;
2011-10-06 22:47:33 +00:00
LOG -> UserLog ( "" , "" , "While loading .sm/.ssc note data, there was an unmatched 2 at beat %f" , NoteRowToBeat ( iRow ) );
2011-03-17 01:47:30 -04:00
out . RemoveTapNote ( t , begin );
}
begin = next ;
}
}
2014-07-23 05:34:23 -06:00
out . RevalidateATIs ( vector < int > (), false );
2011-03-17 01:47:30 -04:00
}
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 < NoteData > 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 );
2014-07-23 05:34:23 -06:00
out . RevalidateATIs ( vector < int > (), false );
2011-03-17 01:47:30 -04:00
}
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 ;
2014-07-25 18:45:02 -05:00
if ( tn . type != TapNoteType_HoldHead )
2011-03-17 01:47:30 -04:00
continue ;
TapNote tail = tn ;
2014-07-25 18:45:02 -05:00
tail . type = TapNoteType_HoldTail ;
2011-03-17 01:47:30 -04:00
/* 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 < NoteData > 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 , ',' );
2011-05-04 16:48:48 -04:00
sRet += ssprintf ( " // measure %d \n " , m );
2011-03-17 01:47:30 -04:00
NoteType nt = GetSmallestNoteTypeForMeasure ( * nd , m );
int iRowSpacing ;
if ( nt == NoteType_Invalid )
iRowSpacing = 1 ;
else
iRowSpacing = lrintf ( 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 )
{
2014-07-25 18:45:02 -05:00
case TapNoteType_Empty : c = '0' ; break ;
case TapNoteType_Tap : c = '1' ; break ;
case TapNoteType_HoldHead :
2011-03-17 01:47:30 -04:00
switch ( tn . subType )
{
2014-07-25 18:45:02 -05:00
case TapNoteSubType_Hold : c = '2' ; break ;
case TapNoteSubType_Roll : c = '4' ; break ;
//case TapNoteSubType_Mine: c = 'N'; break;
2012-12-27 16:59:35 -05:00
default :
FAIL_M ( ssprintf ( "Invalid tap note subtype: %i" , tn . subType ));
2011-03-17 01:47:30 -04:00
}
break ;
2014-07-25 18:45:02 -05:00
case TapNoteType_HoldTail : c = '3' ; break ;
case TapNoteType_Mine : c = 'M' ; break ;
case TapNoteType_Attack : c = 'A' ; break ;
case TapNoteType_AutoKeysound : c = 'K' ; break ;
case TapNoteType_Lift : c = 'L' ; break ;
case TapNoteType_Fake : c = 'F' ; break ;
2011-03-17 01:47:30 -04:00
default :
2012-12-27 16:59:35 -05:00
c = '\0' ;
FAIL_M ( ssprintf ( "Invalid tap note type: %i" , tn . type ));
2011-03-17 01:47:30 -04:00
}
sRet . append ( 1 , c );
2014-07-25 18:45:02 -05:00
if ( tn . type == TapNoteType_Attack )
2011-03-17 01:47:30 -04:00
{
sRet . append ( ssprintf ( "{%s:%.2f}" , tn . sAttackModifiers . c_str (),
tn . fAttackDurationSeconds ) );
}
2014-07-25 18:45:02 -05:00
// hey maybe if we have TapNoteType_Item we can do things here.
2011-03-17 01:47:30 -04:00
if ( tn . iKeysoundIndex >= 0 )
sRet . append ( ssprintf ( "[%d]" , tn . iKeysoundIndex ) );
}
sRet . append ( 1 , '\n' );
}
}
}
}
void NoteDataUtil :: SplitCompositeNoteData ( const NoteData & in , vector < NoteData > & out )
{
if ( ! in . IsComposite () )
{
out . push_back ( in );
return ;
}
FOREACH_PlayerNumber ( pn )
{
out . push_back ( NoteData () );
out . back (). SetNumTracks ( in . GetNumTracks () );
}
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 ;
/*
XXX: This code is (hopefully) a temporary hack to make sure that
routine charts don't have any notes without players assigned to them.
I suspect this is due to a related bug that these problems were
occuring to begin with, but at this time, I am unsure how to deal with it.
Hopefully this hack can be removed soon. -- Jason "Wolfman2000" Felds
*/
2014-12-07 01:53:17 -07:00
const Style * curStyle = GAMESTATE -> GetCurrentStyle ( PLAYER_INVALID );
2011-03-17 01:47:30 -04:00
if ( ( curStyle == NULL || curStyle -> m_StyleType == StyleType_TwoPlayersSharedSides )
&& int ( tn . pn ) > NUM_PlayerNumber )
{
tn . pn = PLAYER_1 ;
}
unsigned index = int ( tn . pn );
ASSERT_M ( index < NUM_PlayerNumber , ssprintf ( "We have a note not assigned to a player. The note in question is on beat %f, column %i." , NoteRowToBeat ( row ), t + 1 ) );
tn . pn = PLAYER_INVALID ;
out [ index ]. SetTapNote ( t , row , tn );
}
}
}
void NoteDataUtil :: CombineCompositeNoteData ( NoteData & out , const vector < NoteData > & 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 ;
2014-07-25 18:45:02 -05:00
if ( i -> second . type == TapNoteType_HoldHead )
2011-03-17 01:47:30 -04:00
out . AddHoldNote ( track , row , row + i -> second . iDuration , i -> second );
else
out . SetTapNote ( track , row , i -> second );
}
}
}
2014-07-23 05:34:23 -06:00
out . RevalidateATIs ( vector < int > (), false );
2011-03-17 01:47:30 -04:00
}
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 < in . GetNumTracks (); t ++ )
{
if ( in . IsHoldNoteAtRow ( t , r - 1 ) &&
in . IsHoldNoteAtRow ( t , r ) )
{
bHoldCrossesThisMeasure = true ;
break ;
}
}
// adjust offset
if ( ! bHoldCrossesThisMeasure )
{
iMeasuresSinceChange = 0 ;
iCurTrackOffset += bOffsetIncreasing ? 1 : - 1 ;
if ( iCurTrackOffset == iTrackOffsetMin || iCurTrackOffset == iTrackOffsetMax )
bOffsetIncreasing ^= true ;
CLAMP ( iCurTrackOffset , iTrackOffsetMin , iTrackOffsetMax );
}
}
iLastMeasure = iMeasure ;
// copy notes in this measure
for ( int t = 0 ; t < in . GetNumTracks (); t ++ )
{
int iOldTrack = t ;
int iNewTrack = ( iOldTrack + iCurTrackOffset ) % iNewNumTracks ;
2015-01-30 10:05:15 -07:00
TapNote tn = in . GetTapNote ( iOldTrack , r );
tn . pn = PLAYER_INVALID ;
2011-03-17 01:47:30 -04:00
out . SetTapNote ( iNewTrack , r , tn );
}
}
2014-07-23 05:34:23 -06:00
out . RevalidateATIs ( vector < int > (), false );
2011-03-17 01:47:30 -04:00
}
void PlaceAutoKeysound ( NoteData & out , int row , TapNote akTap )
{
int iEmptyTrack = - 1 ;
int iEmptyRow = row ;
int iNewNumTracks = out . GetNumTracks ();
bool bFoundEmptyTrack = false ;
int iRowsToLook [ 3 ] = { 0 , - 1 , 1 };
for ( int j = 0 ; j < 3 ; j ++ )
{
int r = iRowsToLook [ j ] + row ;
if ( r < 0 )
continue ;
for ( int i = 0 ; i < iNewNumTracks ; ++ i )
{
if ( out . GetTapNote ( i , r ) == TAP_EMPTY && ! out . IsHoldNoteAtRow ( i , r ) )
{
iEmptyTrack = i ;
iEmptyRow = r ;
bFoundEmptyTrack = true ;
break ;
}
}
if ( bFoundEmptyTrack )
break ;
}
if ( iEmptyTrack != - 1 )
{
2014-07-25 18:45:02 -05:00
akTap . type = TapNoteType_AutoKeysound ;
2011-03-17 01:47:30 -04:00
out . SetTapNote ( iEmptyTrack , iEmptyRow , akTap );
}
}
void NoteDataUtil :: LoadOverlapped ( const NoteData & in , NoteData & out , int iNewNumTracks )
{
out . SetNumTracks ( iNewNumTracks );
/* 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 ( int tr = 0 ; tr < MAX_NOTE_TRACKS ; ++ tr )
{
LastSourceTrack [ tr ] = - 1 ;
LastSourceRow [ tr ] = - MAX_NOTE_ROW ;
DestRow [ tr ] = tr ;
wrap ( DestRow [ tr ], iNewNumTracks );
}
const int ShiftThreshold = BeatToNoteRow ( 1 );
FOREACH_NONEMPTY_ROW_ALL_TRACKS ( in , row )
{
for ( int iTrackFrom = 0 ; iTrackFrom < in . GetNumTracks (); ++ iTrackFrom )
{
2015-01-30 10:05:15 -07:00
TapNote tnFrom = in . GetTapNote ( iTrackFrom , row );
2014-07-25 18:45:02 -05:00
if ( tnFrom . type == TapNoteType_Empty || tnFrom . type == TapNoteType_AutoKeysound )
2011-03-17 01:47:30 -04:00
continue ;
2015-01-30 10:05:15 -07:00
tnFrom . pn = PLAYER_INVALID ;
2011-03-17 01:47:30 -04:00
// If this is a hold note, find the end.
int iEndIndex = row ;
2014-07-25 18:45:02 -05:00
if ( tnFrom . type == TapNoteType_HoldHead )
2011-03-17 01:47:30 -04:00
iEndIndex = row + tnFrom . iDuration ;
int & iTrackTo = DestRow [ iTrackFrom ];
if ( LastSourceTrack [ iTrackTo ] != iTrackFrom )
{
if ( iEndIndex - 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 ( iEndIndex - LastSourceRow [ iTrackTo ] < ShiftThreshold )
{
// If it has a keysound, put it in autokeysound track.
if ( tnFrom . iKeysoundIndex >= 0 )
{
TapNote akTap = tnFrom ;
PlaceAutoKeysound ( out , row , akTap );
}
continue ;
}
}
LastSourceTrack [ iTrackTo ] = iTrackFrom ;
LastSourceRow [ iTrackTo ] = iEndIndex ;
out . SetTapNote ( iTrackTo , row , tnFrom );
2014-07-25 18:45:02 -05:00
if ( tnFrom . type == TapNoteType_HoldHead )
2011-03-17 01:47:30 -04:00
{
const TapNote & tnTail = in . GetTapNote ( iTrackFrom , iEndIndex );
out . SetTapNote ( iTrackTo , iEndIndex , tnTail );
}
}
// find empty track for autokeysounds in 2 next rows, so you can hear most autokeysounds
for ( int iTrackFrom = 0 ; iTrackFrom < in . GetNumTracks (); ++ iTrackFrom )
{
const TapNote & tnFrom = in . GetTapNote ( iTrackFrom , row );
2014-07-25 18:45:02 -05:00
if ( tnFrom . type != TapNoteType_AutoKeysound )
2011-03-17 01:47:30 -04:00
continue ;
PlaceAutoKeysound ( out , row , tnFrom );
}
}
2014-07-23 05:34:23 -06:00
out . RevalidateATIs ( vector < int > (), false );
2011-03-17 01:47:30 -04:00
}
int FindLongestOverlappingHoldNoteForAnyTrack ( const NoteData & in , int iRow )
{
int iMaxTailRow = - 1 ;
for ( int t = 0 ; t < in . GetNumTracks (); t ++ )
{
const TapNote & tn = in . GetTapNote ( t , iRow );
2014-07-25 18:45:02 -05:00
if ( tn . type == TapNoteType_HoldHead )
2011-03-17 01:47:30 -04:00
iMaxTailRow = max ( iMaxTailRow , iRow + tn . iDuration );
}
return iMaxTailRow ;
}
// For every row in "in" with a tap or hold on any track, enable the specified tracks in "out".
void LightTransformHelper ( const NoteData & in , NoteData & out , const vector < int > & 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 < int > aiTracks ;
for ( int i = 0 ; i < out . GetNumTracks (); ++ i )
aiTracks . push_back ( i );
LightTransformHelper ( in , out , aiTracks );
}
// This transform is specific to StepsType_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_CabinetLight ] = { 0 , 1 , 2 , 3 , - 1 , - 1 };
out . LoadTransformed ( transformed_marquee , NUM_CabinetLight , iOriginalTrackToTakeFrom );
}
// For each track in "bass", enable the bass lights.
{
vector < int > aiTracks ;
aiTracks . push_back ( LIGHT_BASS_LEFT );
aiTracks . push_back ( LIGHT_BASS_RIGHT );
LightTransformHelper ( bass , out , aiTracks );
}
// Delete all mines.
NoteDataUtil :: RemoveMines ( out );
}
2015-01-26 19:43:15 -07:00
// This kickbox_limb enum should not be used anywhere outside the
// Autogenkickbox function. -Kyz
enum kickbox_limb
{
left_foot , left_fist , right_fist , right_foot , num_kickbox_limbs , invalid_limb = - 1
};
void NoteDataUtil :: AutogenKickbox ( const NoteData & in , NoteData & out , const TimingData & timing , StepsType out_type , int nonrandom_seed )
{
// Each limb has its own list of tracks it is used for. This allows
// abstract handling of the different styles.
// By convention, the lower panels are pushed first. This gives the upper
// panels a higher index, which is mnemonically useful.
vector < vector < int > > limb_tracks ( num_kickbox_limbs );
bool have_feet = true ;
switch ( out_type )
{
case StepsType_kickbox_human :
out . SetNumTracks ( 4 );
limb_tracks [ left_foot ]. push_back ( 0 );
limb_tracks [ left_fist ]. push_back ( 1 );
limb_tracks [ right_fist ]. push_back ( 2 );
limb_tracks [ right_foot ]. push_back ( 3 );
break ;
case StepsType_kickbox_quadarm :
out . SetNumTracks ( 4 );
have_feet = false ;
limb_tracks [ left_fist ]. push_back ( 1 );
limb_tracks [ left_fist ]. push_back ( 0 );
limb_tracks [ right_fist ]. push_back ( 2 );
limb_tracks [ right_fist ]. push_back ( 3 );
break ;
case StepsType_kickbox_insect :
out . SetNumTracks ( 6 );
limb_tracks [ left_foot ]. push_back ( 0 );
limb_tracks [ left_fist ]. push_back ( 2 );
limb_tracks [ left_fist ]. push_back ( 1 );
limb_tracks [ right_fist ]. push_back ( 3 );
limb_tracks [ right_fist ]. push_back ( 4 );
limb_tracks [ right_foot ]. push_back ( 5 );
break ;
case StepsType_kickbox_arachnid :
out . SetNumTracks ( 8 );
limb_tracks [ left_foot ]. push_back ( 0 );
limb_tracks [ left_foot ]. push_back ( 1 );
limb_tracks [ left_fist ]. push_back ( 3 );
limb_tracks [ left_fist ]. push_back ( 2 );
limb_tracks [ right_fist ]. push_back ( 4 );
limb_tracks [ right_fist ]. push_back ( 5 );
limb_tracks [ right_foot ]. push_back ( 7 );
limb_tracks [ right_foot ]. push_back ( 6 );
break ;
DEFAULT_FAIL ( out_type );
}
// prev_limb_panels keeps track of which panel in the track list the limb
// hit last.
vector < size_t > prev_limb_panels ( num_kickbox_limbs , 0 );
vector < int > panel_repeat_counts ( num_kickbox_limbs , 0 );
vector < int > panel_repeat_goals ( num_kickbox_limbs , 0 );
RandomGen rnd ( nonrandom_seed );
kickbox_limb prev_limb_used = invalid_limb ;
// Kicks are only allowed if there is enough setup/recovery time.
float kick_recover_time = GAMESTATE -> GetAutoGenFarg ( 0 );
if ( kick_recover_time <= 0.0f )
{
kick_recover_time = .25f ;
}
float prev_note_time = - 1.0f ;
int rows_done = 0 ;
#define RAND_FIST ((rnd() % 2) ? left_fist : right_fist)
#define RAND_FOOT ((rnd() % 2) ? left_foot : right_foot)
FOREACH_NONEMPTY_ROW_ALL_TRACKS ( in , r )
{
// Arbitrary: Drop everything except taps and hold heads out entirely,
// convert holds to tap just the head.
bool has_valid_tapnote = false ;
for ( int t = 0 ; t < in . GetNumTracks (); ++ t )
{
const TapNote & tn = in . GetTapNote ( t , r );
if ( tn . type == TapNoteType_Tap || tn . type == TapNoteType_HoldHead )
{
has_valid_tapnote = true ;
break ;
}
}
if ( ! has_valid_tapnote ) { continue ; }
int next_row = r ;
bool next_has_valid = false ;
while ( ! next_has_valid )
{
if ( ! in . GetNextTapNoteRowForAllTracks ( next_row ))
{
next_row = - 1 ;
next_has_valid = true ;
}
else
{
for ( int t = 0 ; t < in . GetNumTracks (); ++ t )
{
const TapNote & tn = in . GetTapNote ( t , next_row );
if ( tn . type == TapNoteType_Tap || tn . type == TapNoteType_HoldHead )
{
next_has_valid = true ;
break ;
}
}
}
}
float this_note_time = timing . GetElapsedTimeFromBeat ( NoteRowToBeat ( r ));
float next_note_time = timing . GetElapsedTimeFromBeat ( NoteRowToBeat ( next_row ));
kickbox_limb this_limb = invalid_limb ;
switch ( prev_limb_used )
{
case invalid_limb :
// First limb is arbitrarily always a fist.
this_limb = RAND_FIST ;
break ;
case left_foot :
case right_foot :
// Multiple kicks in a row are allowed if they're on the same foot.
// Allow the last note to be a kick.
// Switch feet if there's enough time.
if ( next_note_time - this_note_time > kick_recover_time * 2.0f )
{
this_limb = prev_limb_used == left_foot ? right_foot : left_foot ;
}
else if (( next_note_time - this_note_time > kick_recover_time * .5f ||
next_note_time < 0.0f ) && ( rnd () % 2 ))
{
this_limb = prev_limb_used ;
}
else
{
this_limb = RAND_FIST ;
}
break ;
case left_fist :
case right_fist :
if ( this_note_time - prev_note_time > kick_recover_time &&
( next_note_time - this_note_time > kick_recover_time ||
next_note_time < 0.0f ) && have_feet )
{
this_limb = RAND_FOOT ;
}
else
{
// Alternate fists.
this_limb = prev_limb_used == left_fist ? right_fist : left_fist ;
}
break ;
}
size_t this_panel = prev_limb_panels [ this_limb ];
if ( panel_repeat_counts [ this_limb ] + 1 > panel_repeat_goals [ this_limb ])
{
// Use a different panel.
this_panel = ( this_panel + 1 ) % limb_tracks [ this_limb ]. size ();
panel_repeat_counts [ this_limb ] = 0 ;
panel_repeat_goals [ this_limb ] = ( rnd () % 8 ) + 1 ;
}
out . SetTapNote ( limb_tracks [ this_limb ][ this_panel ], r , TAP_ORIGINAL_TAP );
++ panel_repeat_counts [ this_limb ];
prev_limb_panels [ this_limb ] = this_panel ;
prev_note_time = this_note_time ;
prev_limb_used = this_limb ;
++ rows_done ;
}
out . RevalidateATIs ( vector < int > (), false );
}
2015-04-06 21:49:46 -06:00
struct recent_note
2011-06-01 16:01:14 +07:00
{
2015-03-23 11:35:30 -06:00
int row ;
int track ;
2015-04-06 21:49:46 -06:00
recent_note ()
2015-03-23 11:35:30 -06:00
: row ( 0 ), track ( 0 ) {}
2015-04-06 21:49:46 -06:00
recent_note ( int r , int t )
2015-03-23 11:35:30 -06:00
: row ( r ), track ( t ) {}
};
2011-06-01 16:01:14 +07:00
2015-04-06 18:45:23 -06:00
// CalculateRadarValues has to delay some stuff until a row ends, but can
// only detect a row ending when it hits the next note. There isn't a note
// after the last row, so it also has to do the delayed stuff after exiting
// its loop. So this state structure exists to be passed to a function that
// can be called from both places to do the work. If this were Lua,
// DoRowEndRadarCalc would be a nested function. -Kyz
2015-04-06 21:49:46 -06:00
struct crv_state
2015-04-06 18:45:23 -06:00
{
bool judgable ;
// hold_ends tracks where currently active holds will end, which is used
// to count the number of hands. -Kyz
vector < int > hold_ends ;
// num_holds_on_curr_row saves us the work of tracking where holds started
// just to keep a jump of two holds from counting as a hand.
int num_holds_on_curr_row ;
int num_notes_on_curr_row ;
2015-04-06 21:49:46 -06:00
crv_state ()
2015-04-06 18:45:23 -06:00
: judgable ( false ), num_holds_on_curr_row ( 0 ), num_notes_on_curr_row ( 0 )
{}
};
2015-04-06 21:49:46 -06:00
static void DoRowEndRadarCalc ( crv_state & state , RadarValues & out )
2015-04-06 18:45:23 -06:00
{
if ( state . judgable )
{
if ( state . num_notes_on_curr_row + ( state . hold_ends . size () -
state . num_holds_on_curr_row ) >= 3 )
{
++ out [ RadarCategory_Hands ];
}
}
}
2011-03-17 01:47:30 -04:00
void NoteDataUtil :: CalculateRadarValues ( const NoteData & in , float fSongSeconds , RadarValues & out )
{
2015-04-06 18:45:23 -06:00
// Anybody editing this function should also examine
// NoteDataWithScoring::GetActualRadarValues to make sure it handles things
// the same way.
2015-03-27 17:04:58 -06:00
out . Zero ();
2015-03-23 11:35:30 -06:00
int curr_row = - 1 ;
// recent_notes is used to calculate the voltage. Each element is the row
// and track number of a tap note. When the pair at the beginning is too
// old, it's deleted. This provides a way to have a rolling window
// that scans for the peak step density. -Kyz
2015-04-06 21:49:46 -06:00
vector < recent_note > recent_notes ;
2015-03-23 11:35:30 -06:00
NoteData :: all_tracks_const_iterator curr_note =
in . GetTapNoteRangeAllTracks ( 0 , MAX_NOTE_ROW );
TimingData * timing = GAMESTATE -> GetProcessedTimingData ();
// total_taps exists because the stream calculation needs GetNumTapNotes,
// but TapsAndHolds + Jumps + Hands would be inaccurate. -Kyz
float total_taps = 0 ;
const float voltage_window_beats = 8.0f ;
const int voltage_window = BeatToNoteRow ( voltage_window_beats );
size_t max_notes_in_voltage_window = 0 ;
int num_chaos_rows = 0 ;
2015-04-06 21:49:46 -06:00
crv_state state ;
2015-03-23 11:35:30 -06:00
while ( ! curr_note . IsAtEnd ())
2011-03-17 01:47:30 -04:00
{
2015-03-23 11:35:30 -06:00
if ( curr_note . Row () != curr_row )
2011-03-17 01:47:30 -04:00
{
2015-04-06 18:45:23 -06:00
DoRowEndRadarCalc ( state , out );
2015-03-23 11:35:30 -06:00
curr_row = curr_note . Row ();
2015-04-06 18:45:23 -06:00
state . num_notes_on_curr_row = 0 ;
state . num_holds_on_curr_row = 0 ;
state . judgable = timing -> IsJudgableAtRow ( curr_row );
for ( size_t n = 0 ; n < state . hold_ends . size (); ++ n )
2015-03-23 11:35:30 -06:00
{
2015-04-06 18:45:23 -06:00
if ( state . hold_ends [ n ] < curr_row )
2015-03-23 11:35:30 -06:00
{
2015-04-06 18:45:23 -06:00
state . hold_ends . erase ( state . hold_ends . begin () + n );
2015-03-23 11:35:30 -06:00
-- n ;
}
}
for ( size_t n = 0 ; n < recent_notes . size (); ++ n )
{
if ( recent_notes [ n ]. row < curr_row - voltage_window )
{
recent_notes . erase ( recent_notes . begin () + n );
-- n ;
}
else
{
// recent_notes is kept sorted, so reaching the first note that
// isn't old enough to remove means we're finished. -Kyz
break ;
}
}
// GetChaosRadarValue did not care about whether a row is judgable.
// So chaos is checked here. -Kyz
if ( GetNoteType ( curr_row ) >= NOTE_TYPE_12TH )
{
++ num_chaos_rows ;
}
2011-03-17 01:47:30 -04:00
}
2015-04-06 18:45:23 -06:00
if ( state . judgable )
2015-03-23 11:35:30 -06:00
{
switch ( curr_note -> type )
{
case TapNoteType_Tap :
case TapNoteType_HoldHead :
2015-04-06 18:45:23 -06:00
// Lifts have to be counted with taps for them to be added to max dp
// correctly. -Kyz
case TapNoteType_Lift :
2015-04-08 20:15:18 -06:00
// HoldTails and Attacks are counted by IsTap. But it doesn't
// make sense to count HoldTails as hittable notes. -Kyz
2015-03-23 11:35:30 -06:00
case TapNoteType_Attack :
2015-04-06 18:45:23 -06:00
++ out [ RadarCategory_Notes ];
++ state . num_notes_on_curr_row ;
++ total_taps ;
recent_notes . push_back (
2015-04-06 21:49:46 -06:00
recent_note ( curr_row , curr_note . Track ()));
2015-04-06 18:45:23 -06:00
max_notes_in_voltage_window = max ( recent_notes . size (),
max_notes_in_voltage_window );
2015-03-23 11:35:30 -06:00
// If there is one hold active, and one tap on this row, it does
// not count as a jump. Hands do need to count the number of
// holds active though. -Kyz
2015-04-06 18:45:23 -06:00
switch ( state . num_notes_on_curr_row )
2015-03-23 11:35:30 -06:00
{
case 1 :
++ out [ RadarCategory_TapsAndHolds ];
break ;
case 2 :
++ out [ RadarCategory_Jumps ];
break ;
default :
break ;
}
if ( curr_note -> type == TapNoteType_HoldHead )
{
2015-04-06 18:45:23 -06:00
state . hold_ends . push_back ( curr_row + curr_note -> iDuration );
++ state . num_holds_on_curr_row ;
2015-03-23 11:35:30 -06:00
switch ( curr_note -> subType )
{
case TapNoteSubType_Hold :
++ out [ RadarCategory_Holds ];
break ;
case TapNoteSubType_Roll :
++ out [ RadarCategory_Rolls ];
break ;
default :
break ;
}
}
2015-04-06 18:45:23 -06:00
else if ( curr_note -> type == TapNoteType_Lift )
{
++ out [ RadarCategory_Lifts ];
}
2015-03-23 11:35:30 -06:00
break ;
case TapNoteType_Mine :
++ out [ RadarCategory_Mines ];
break ;
case TapNoteType_Fake :
++ out [ RadarCategory_Fakes ];
break ;
}
}
else
{
++ out [ RadarCategory_Fakes ];
}
++ curr_note ;
2011-03-17 01:47:30 -04:00
}
2015-04-06 18:45:23 -06:00
DoRowEndRadarCalc ( state , out );
2015-03-23 11:35:30 -06:00
// Walking the notes complete, now assign any values that remain. -Kyz
if ( fSongSeconds > 0.0f )
2011-03-17 01:47:30 -04:00
{
2015-04-06 18:45:23 -06:00
out [ RadarCategory_Stream ] = ( total_taps / fSongSeconds ) / 7.0f ;
2015-03-23 11:35:30 -06:00
// As seen in GetVoltageRadarValue: Don't use the timing data, just
// pretend the beats are evenly spaced. -Kyz
2015-03-23 22:41:15 -06:00
float avg_bps = in . GetLastBeat () / fSongSeconds ;
2015-04-06 18:45:23 -06:00
out [ RadarCategory_Voltage ] =
2015-03-23 11:35:30 -06:00
(( max_notes_in_voltage_window / voltage_window_beats ) * avg_bps ) /
2015-04-06 18:45:23 -06:00
10.0f ;
out [ RadarCategory_Air ] = out [ RadarCategory_Jumps ] / fSongSeconds ;
out [ RadarCategory_Freeze ] = out [ RadarCategory_Holds ] / fSongSeconds ;
out [ RadarCategory_Chaos ] = num_chaos_rows / fSongSeconds * .5f ;
2011-03-17 01:47:30 -04:00
}
2015-03-23 11:35:30 -06:00
// Sorry, there's not an assert here anymore for making sure all fields
// are set. There's a comment in the RadarCategory enum to direct
// attention here when adding new categories. -Kyz
2011-03-17 01:47:30 -04:00
}
void NoteDataUtil :: RemoveHoldNotes ( NoteData & in , int iStartIndex , int iEndIndex )
{
// turn all the HoldNotes into TapNotes
for ( int t = 0 ; t < in . GetNumTracks (); ++ t )
{
NoteData :: TrackMap :: iterator begin , end ;
in . GetTapNoteRangeInclusive ( t , iStartIndex , iEndIndex , begin , end );
for ( ; begin != end ; ++ begin )
{
2014-07-25 18:45:02 -05:00
if ( begin -> second . type != TapNoteType_HoldHead ||
begin -> second . subType != TapNoteSubType_Hold )
2011-03-17 01:47:30 -04:00
continue ;
2014-07-25 18:45:02 -05:00
begin -> second . type = TapNoteType_Tap ;
2011-03-17 01:47:30 -04:00
}
}
2014-07-23 05:34:23 -06:00
in . RevalidateATIs ( vector < int > (), false );
2011-03-17 01:47:30 -04:00
}
void NoteDataUtil :: ChangeRollsToHolds ( NoteData & in , int iStartIndex , int iEndIndex )
{
for ( int t = 0 ; t < in . GetNumTracks (); ++ t )
{
NoteData :: TrackMap :: iterator begin , end ;
in . GetTapNoteRangeInclusive ( t , iStartIndex , iEndIndex , begin , end );
for ( ; begin != end ; ++ begin )
{
2014-07-25 18:45:02 -05:00
if ( begin -> second . type != TapNoteType_HoldHead ||
begin -> second . subType != TapNoteSubType_Roll )
2011-03-17 01:47:30 -04:00
continue ;
2014-07-25 18:45:02 -05:00
begin -> second . subType = TapNoteSubType_Hold ;
2011-03-17 01:47:30 -04:00
}
}
2014-07-23 05:34:23 -06:00
in . RevalidateATIs ( vector < int > (), false );
2011-03-17 01:47:30 -04:00
}
void NoteDataUtil :: ChangeHoldsToRolls ( NoteData & in , int iStartIndex , int iEndIndex )
{
for ( int t = 0 ; t < in . GetNumTracks (); ++ t )
{
NoteData :: TrackMap :: iterator begin , end ;
in . GetTapNoteRangeInclusive ( t , iStartIndex , iEndIndex , begin , end );
for ( ; begin != end ; ++ begin )
{
2014-07-25 18:45:02 -05:00
if ( begin -> second . type != TapNoteType_HoldHead ||
begin -> second . subType != TapNoteSubType_Hold )
2011-03-17 01:47:30 -04:00
continue ;
2014-07-25 18:45:02 -05:00
begin -> second . subType = TapNoteSubType_Roll ;
2011-03-17 01:47:30 -04:00
}
}
2014-07-23 05:34:23 -06:00
in . RevalidateATIs ( vector < int > (), false );
2011-03-17 01:47:30 -04:00
}
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 < NoteData > 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 < int > 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 && t < in . GetNumTracks (); t ++ )
{
const TapNote & tn = in . GetTapNote ( t , r );
2014-07-25 18:45:02 -05:00
if ( tn . type == TapNoteType_Tap || tn . type == TapNoteType_HoldHead )
2011-03-17 01:47:30 -04:00
{
in . SetTapNote ( t , r , TAP_EMPTY );
iTracksToRemove -- ;
}
}
}
2014-07-23 05:34:23 -06:00
in . RevalidateATIs ( vector < int > (), false );
2011-03-17 01:47:30 -04:00
}
void NoteDataUtil :: RemoveJumps ( NoteData & inout , int iStartIndex , int iEndIndex )
{
RemoveSimultaneousNotes ( inout , 1 , iStartIndex , iEndIndex );
}
void NoteDataUtil :: RemoveHands ( NoteData & inout , int iStartIndex , int iEndIndex )
{
RemoveSimultaneousNotes ( inout , 2 , iStartIndex , iEndIndex );
}
void NoteDataUtil :: RemoveQuads ( NoteData & inout , int iStartIndex , int iEndIndex )
{
RemoveSimultaneousNotes ( inout , 3 , iStartIndex , iEndIndex );
}
2014-07-25 18:45:02 -05:00
void NoteDataUtil :: RemoveSpecificTapNotes ( NoteData & inout , TapNoteType tn , int iStartIndex , int iEndIndex )
2011-03-17 01:47:30 -04:00
{
for ( int t = 0 ; t < inout . GetNumTracks (); t ++ )
FOREACH_NONEMPTY_ROW_IN_TRACK_RANGE ( inout , t , r , iStartIndex , iEndIndex )
if ( inout . GetTapNote ( t , r ). type == tn )
inout . SetTapNote ( t , r , TAP_EMPTY );
2014-07-23 05:34:23 -06:00
inout . RevalidateATIs ( vector < int > (), false );
2011-03-17 01:47:30 -04:00
}
void NoteDataUtil :: RemoveMines ( NoteData & inout , int iStartIndex , int iEndIndex )
{
2014-07-25 18:45:02 -05:00
RemoveSpecificTapNotes ( inout , TapNoteType_Mine , iStartIndex , iEndIndex );
2011-03-17 01:47:30 -04:00
}
void NoteDataUtil :: RemoveLifts ( NoteData & inout , int iStartIndex , int iEndIndex )
{
2014-07-25 18:45:02 -05:00
RemoveSpecificTapNotes ( inout , TapNoteType_Lift , iStartIndex , iEndIndex );
2011-03-17 01:47:30 -04:00
}
void NoteDataUtil :: RemoveFakes ( NoteData & inout , int iStartIndex , int iEndIndex )
{
2014-07-25 18:45:02 -05:00
RemoveSpecificTapNotes ( inout , TapNoteType_Fake , iStartIndex , iEndIndex );
2011-03-17 01:47:30 -04:00
}
void NoteDataUtil :: RemoveAllButOneTap ( NoteData & inout , int row )
{
if ( row < 0 ) return ;
int track ;
for ( track = 0 ; track < inout . GetNumTracks (); ++ track )
{
2014-07-25 18:45:02 -05:00
if ( inout . GetTapNote ( track , row ). type == TapNoteType_Tap )
2011-03-17 01:47:30 -04:00
break ;
}
track ++ ;
for ( ; track < inout . GetNumTracks (); ++ track )
{
NoteData :: iterator iter = inout . FindTapNote ( track , row );
2014-07-25 18:45:02 -05:00
if ( iter != inout . end ( track ) && iter -> second . type == TapNoteType_Tap )
2011-03-17 01:47:30 -04:00
inout . RemoveTapNote ( track , iter );
}
2014-07-23 05:34:23 -06:00
inout . RevalidateATIs ( vector < int > (), false );
2011-03-17 01:47:30 -04:00
}
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 ;
}
}
2014-07-23 05:34:23 -06:00
inout . RevalidateATIs ( vector < int > (), false );
2011-03-17 01:47:30 -04:00
}
2011-10-16 16:33:33 -04:00
// TODO: Perform appropriate matrix calculations for everything instead.
2011-03-17 01:47:30 -04:00
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 StepsType_dance_single :
case StepsType_dance_double :
case StepsType_dance_couple :
case StepsType_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 StepsType_dance_solo :
iTakeFromTrack [ 0 ] = 5 ;
iTakeFromTrack [ 1 ] = 4 ;
iTakeFromTrack [ 2 ] = 0 ;
iTakeFromTrack [ 3 ] = 3 ;
iTakeFromTrack [ 4 ] = 1 ;
iTakeFromTrack [ 5 ] = 2 ;
break ;
case StepsType_pump_single :
case StepsType_pump_couple :
2011-10-04 00:53:33 -04:00
iTakeFromTrack [ 0 ] = 1 ;
iTakeFromTrack [ 1 ] = 3 ;
2011-03-17 01:47:30 -04:00
iTakeFromTrack [ 2 ] = 2 ;
2011-10-04 00:53:33 -04:00
iTakeFromTrack [ 3 ] = 4 ;
iTakeFromTrack [ 4 ] = 0 ;
iTakeFromTrack [ 5 ] = 6 ;
iTakeFromTrack [ 6 ] = 8 ;
2011-03-17 01:47:30 -04:00
iTakeFromTrack [ 7 ] = 7 ;
2011-10-04 00:53:33 -04:00
iTakeFromTrack [ 8 ] = 9 ;
iTakeFromTrack [ 9 ] = 5 ;
2011-03-17 01:47:30 -04:00
break ;
case StepsType_pump_halfdouble :
iTakeFromTrack [ 0 ] = 2 ;
iTakeFromTrack [ 1 ] = 0 ;
iTakeFromTrack [ 2 ] = 1 ;
iTakeFromTrack [ 3 ] = 3 ;
iTakeFromTrack [ 4 ] = 4 ;
iTakeFromTrack [ 5 ] = 5 ;
break ;
case StepsType_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 ;
2011-10-16 15:50:13 -04:00
case NoteDataUtil :: backwards :
{
// If a Pump game type, treat differently. Otherwise, send to mirror.
2011-10-16 16:43:20 -04:00
bool needsBackwards = true ;
2011-10-16 15:50:13 -04:00
switch ( st )
{
case StepsType_pump_single :
case StepsType_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 ] = 2 ;
iTakeFromTrack [ 8 ] = 5 ;
iTakeFromTrack [ 9 ] = 6 ;
break ;
}
case StepsType_pump_double :
case StepsType_pump_routine :
{
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 ;
}
case StepsType_pump_halfdouble :
{
iTakeFromTrack [ 0 ] = 5 ;
iTakeFromTrack [ 1 ] = 3 ;
iTakeFromTrack [ 2 ] = 4 ;
iTakeFromTrack [ 3 ] = 1 ;
iTakeFromTrack [ 4 ] = 2 ;
iTakeFromTrack [ 5 ] = 0 ;
break ;
}
2015-04-30 12:51:23 +01:00
case StepsType_beat_single5 :
{
// scratch is on right (cols 5 and 11) for 5-key
iTakeFromTrack [ 0 ] = 4 ;
iTakeFromTrack [ 1 ] = 3 ;
iTakeFromTrack [ 2 ] = 2 ;
iTakeFromTrack [ 3 ] = 1 ;
iTakeFromTrack [ 4 ] = 0 ;
iTakeFromTrack [ 5 ] = 5 ;
iTakeFromTrack [ 6 ] = 10 ;
iTakeFromTrack [ 7 ] = 9 ;
iTakeFromTrack [ 8 ] = 8 ;
iTakeFromTrack [ 9 ] = 7 ;
iTakeFromTrack [ 10 ] = 6 ;
iTakeFromTrack [ 11 ] = 11 ;
break ;
}
case StepsType_beat_single7 :
{
// scratch is on left (cols 0 and 8) for 7-key
iTakeFromTrack [ 0 ] = 0 ;
iTakeFromTrack [ 1 ] = 7 ;
iTakeFromTrack [ 2 ] = 6 ;
iTakeFromTrack [ 3 ] = 5 ;
iTakeFromTrack [ 4 ] = 4 ;
iTakeFromTrack [ 5 ] = 3 ;
iTakeFromTrack [ 6 ] = 2 ;
iTakeFromTrack [ 7 ] = 1 ;
iTakeFromTrack [ 8 ] = 8 ;
iTakeFromTrack [ 9 ] = 15 ;
iTakeFromTrack [ 10 ] = 14 ;
iTakeFromTrack [ 11 ] = 13 ;
iTakeFromTrack [ 12 ] = 12 ;
iTakeFromTrack [ 13 ] = 11 ;
iTakeFromTrack [ 14 ] = 10 ;
iTakeFromTrack [ 15 ] = 9 ;
break ;
}
2011-10-16 16:43:20 -04:00
default :
needsBackwards = false ;
2011-10-16 15:50:13 -04:00
}
2011-10-16 16:43:20 -04:00
if ( needsBackwards ) break ;
2011-10-16 15:50:13 -04:00
}
2011-03-17 01:47:30 -04:00
case NoteDataUtil :: mirror :
2011-10-04 00:53:33 -04:00
{
2011-10-16 15:50:13 -04:00
for ( int t = 0 ; t < NumTracks ; t ++ )
iTakeFromTrack [ t ] = NumTracks - t - 1 ;
2011-10-04 00:53:33 -04:00
break ;
}
2011-03-17 01:47:30 -04:00
case NoteDataUtil :: shuffle :
case NoteDataUtil :: super_shuffle : // use shuffle code to mix up HoldNotes without creating impossible patterns
{
// TRICKY: Shuffle so that both player get the same shuffle mapping
2015-04-30 12:51:23 +01:00
// in the same round. This is already achieved in beat mode.
2011-03-17 01:47:30 -04:00
int iOrig [ MAX_NOTE_TRACKS ];
memcpy ( iOrig , iTakeFromTrack , sizeof ( iOrig ) );
int iShuffleSeed = GAMESTATE -> m_iStageSeed ;
do {
RandomGen rnd ( iShuffleSeed );
2015-04-30 12:51:23 +01:00
// ignore turntable in beat mode
switch ( st ) {
case StepsType_beat_single5 :
{
random_shuffle ( & iTakeFromTrack [ 0 ], & iTakeFromTrack [ 5 ], rnd );
random_shuffle ( & iTakeFromTrack [ 6 ], & iTakeFromTrack [ 11 ], rnd );
break ;
}
case StepsType_beat_single7 :
{
random_shuffle ( & iTakeFromTrack [ 1 ], & iTakeFromTrack [ 8 ], rnd );
random_shuffle ( & iTakeFromTrack [ 9 ], & iTakeFromTrack [ 16 ], rnd );
break ;
}
default :
{
random_shuffle ( & iTakeFromTrack [ 0 ], & iTakeFromTrack [ NumTracks ], rnd );
break ;
}
}
2011-03-17 01:47:30 -04:00
iShuffleSeed ++ ;
}
2015-04-30 12:51:23 +01:00
while ( ! memcmp ( iOrig , iTakeFromTrack , sizeof ( iOrig ) ) ); // shuffle again if shuffle managed to shuffle them in the same order
2011-03-17 01:47:30 -04:00
}
break ;
case NoteDataUtil :: soft_shuffle :
{
// XXX: this is still pretty much a stub.
// soft shuffle, as described at
// http://www.stepmania.com/forums/showthread.php?t=19469
/* one of the following at random:
*
* 0. No columns changed
* 1. Left and right columns swapped
* 2. Down and up columns swapped
* 3. Mirror (left and right swapped, down and up swapped)
* ----------------------------------------------------------------
* To extend it to handle all game types, it would pick each axis
* of symmetry the game type has and either flip it or not flip it.
*
* For instance, PIU singles has four axes:
* horizontal, vertical,
* diagonally top left to bottom right,
* diagonally bottom left to top right.
* (above text from forums) */
// TRICKY: Shuffle so that both player get the same shuffle mapping
// in the same round.
int iShuffleSeed = GAMESTATE -> m_iStageSeed ;
RandomGen rnd ( iShuffleSeed );
int iRandChoice = ( rnd () % 4 );
// XXX: cases 1 and 2 only implemented for dance_*
switch ( iRandChoice )
{
case 1 : // left and right mirror
case 2 : // up and down mirror
switch ( st )
{
case StepsType_dance_single :
if ( iRandChoice == 1 )
{
// left and right
iTakeFromTrack [ 0 ] = 3 ;
iTakeFromTrack [ 3 ] = 0 ;
}
if ( iRandChoice == 2 )
{
// up and down
iTakeFromTrack [ 1 ] = 2 ;
iTakeFromTrack [ 2 ] = 1 ;
}
break ;
case StepsType_dance_double :
case StepsType_dance_couple :
case StepsType_dance_routine :
if ( iRandChoice == 1 )
{
// left and right
iTakeFromTrack [ 0 ] = 3 ;
iTakeFromTrack [ 3 ] = 0 ;
iTakeFromTrack [ 4 ] = 7 ;
iTakeFromTrack [ 7 ] = 4 ;
}
if ( iRandChoice == 2 )
{
// up and down
iTakeFromTrack [ 1 ] = 2 ;
iTakeFromTrack [ 2 ] = 1 ;
iTakeFromTrack [ 5 ] = 6 ;
iTakeFromTrack [ 6 ] = 5 ;
}
break ;
// here be dragons (unchanged code)
case StepsType_dance_solo :
iTakeFromTrack [ 0 ] = 5 ;
iTakeFromTrack [ 1 ] = 4 ;
iTakeFromTrack [ 2 ] = 0 ;
iTakeFromTrack [ 3 ] = 3 ;
iTakeFromTrack [ 4 ] = 1 ;
iTakeFromTrack [ 5 ] = 2 ;
break ;
case StepsType_pump_single :
case StepsType_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 StepsType_pump_halfdouble :
iTakeFromTrack [ 0 ] = 2 ;
iTakeFromTrack [ 1 ] = 0 ;
iTakeFromTrack [ 2 ] = 1 ;
iTakeFromTrack [ 3 ] = 3 ;
iTakeFromTrack [ 4 ] = 4 ;
iTakeFromTrack [ 5 ] = 5 ;
break ;
case StepsType_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 ;
2015-01-27 17:27:02 -07:00
case StepsType_kickbox_human :
if ( iRandChoice == 1 )
{
iTakeFromTrack [ 0 ] = 3 ;
iTakeFromTrack [ 3 ] = 0 ;
}
if ( iRandChoice == 2 )
{
iTakeFromTrack [ 1 ] = 2 ;
iTakeFromTrack [ 2 ] = 1 ;
}
break ;
case StepsType_kickbox_quadarm :
if ( iRandChoice == 1 )
{
iTakeFromTrack [ 0 ] = 1 ;
iTakeFromTrack [ 1 ] = 0 ;
iTakeFromTrack [ 2 ] = 3 ;
iTakeFromTrack [ 3 ] = 2 ;
}
if ( iRandChoice == 2 )
{
iTakeFromTrack [ 1 ] = 2 ;
iTakeFromTrack [ 2 ] = 1 ;
}
break ;
case StepsType_kickbox_insect :
if ( iRandChoice == 1 )
{
iTakeFromTrack [ 1 ] = 2 ;
iTakeFromTrack [ 2 ] = 1 ;
iTakeFromTrack [ 3 ] = 4 ;
iTakeFromTrack [ 4 ] = 3 ;
}
if ( iRandChoice == 2 )
{
iTakeFromTrack [ 1 ] = 4 ;
iTakeFromTrack [ 2 ] = 3 ;
iTakeFromTrack [ 3 ] = 2 ;
iTakeFromTrack [ 4 ] = 1 ;
}
break ;
case StepsType_kickbox_arachnid :
if ( iRandChoice == 1 )
{
iTakeFromTrack [ 0 ] = 1 ;
iTakeFromTrack [ 1 ] = 0 ;
iTakeFromTrack [ 2 ] = 3 ;
iTakeFromTrack [ 3 ] = 2 ;
iTakeFromTrack [ 4 ] = 5 ;
iTakeFromTrack [ 5 ] = 4 ;
iTakeFromTrack [ 6 ] = 7 ;
iTakeFromTrack [ 7 ] = 6 ;
}
if ( iRandChoice == 2 )
{
iTakeFromTrack [ 0 ] = 6 ;
iTakeFromTrack [ 1 ] = 7 ;
iTakeFromTrack [ 2 ] = 4 ;
iTakeFromTrack [ 3 ] = 5 ;
iTakeFromTrack [ 4 ] = 2 ;
iTakeFromTrack [ 5 ] = 3 ;
iTakeFromTrack [ 6 ] = 0 ;
iTakeFromTrack [ 7 ] = 1 ;
}
break ;
2011-03-17 01:47:30 -04:00
default : break ;
}
break ;
case 3 : // full mirror
GetTrackMapping ( st , NoteDataUtil :: mirror , NumTracks , iTakeFromTrack );
break ;
case 0 :
default :
// case 0 and default are set by identity matrix above
break ;
}
}
break ;
case NoteDataUtil :: stomp :
switch ( st )
{
case StepsType_dance_single :
case StepsType_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 StepsType_dance_double :
case StepsType_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 ;
2015-01-27 17:27:02 -07:00
case StepsType_kickbox_human :
iTakeFromTrack [ 0 ] = 1 ;
iTakeFromTrack [ 1 ] = 0 ;
iTakeFromTrack [ 2 ] = 3 ;
iTakeFromTrack [ 3 ] = 2 ;
break ;
case StepsType_kickbox_quadarm :
iTakeFromTrack [ 0 ] = 1 ;
iTakeFromTrack [ 1 ] = 0 ;
iTakeFromTrack [ 2 ] = 3 ;
iTakeFromTrack [ 3 ] = 2 ;
break ;
case StepsType_kickbox_insect :
iTakeFromTrack [ 0 ] = 1 ;
iTakeFromTrack [ 1 ] = 4 ;
iTakeFromTrack [ 2 ] = 3 ;
iTakeFromTrack [ 3 ] = 2 ;
iTakeFromTrack [ 4 ] = 1 ;
iTakeFromTrack [ 5 ] = 4 ;
break ;
case StepsType_kickbox_arachnid :
iTakeFromTrack [ 0 ] = 5 ;
iTakeFromTrack [ 1 ] = 4 ;
iTakeFromTrack [ 2 ] = 5 ;
iTakeFromTrack [ 3 ] = 4 ;
iTakeFromTrack [ 4 ] = 3 ;
iTakeFromTrack [ 5 ] = 2 ;
iTakeFromTrack [ 6 ] = 1 ;
iTakeFromTrack [ 7 ] = 0 ;
break ;
2011-03-17 01:47:30 -04:00
default :
break ;
}
break ;
2014-08-30 00:47:07 -06:00
case NoteDataUtil :: swap_up_down :
switch ( st )
{
case StepsType_dance_single :
case StepsType_dance_double :
case StepsType_dance_couple :
case StepsType_dance_routine :
iTakeFromTrack [ 0 ] = 0 ;
iTakeFromTrack [ 1 ] = 2 ;
iTakeFromTrack [ 2 ] = 1 ;
iTakeFromTrack [ 3 ] = 3 ;
iTakeFromTrack [ 4 ] = 4 ;
iTakeFromTrack [ 5 ] = 6 ;
iTakeFromTrack [ 6 ] = 5 ;
iTakeFromTrack [ 7 ] = 7 ;
break ;
case StepsType_pump_single :
case StepsType_pump_double :
case StepsType_pump_couple :
case StepsType_pump_routine :
iTakeFromTrack [ 0 ] = 1 ;
iTakeFromTrack [ 1 ] = 0 ;
iTakeFromTrack [ 2 ] = 2 ;
iTakeFromTrack [ 3 ] = 4 ;
iTakeFromTrack [ 4 ] = 3 ;
iTakeFromTrack [ 5 ] = 6 ;
iTakeFromTrack [ 6 ] = 5 ;
iTakeFromTrack [ 7 ] = 7 ;
iTakeFromTrack [ 8 ] = 9 ;
iTakeFromTrack [ 9 ] = 8 ;
break ;
case StepsType_pump_halfdouble :
iTakeFromTrack [ 0 ] = 0 ;
iTakeFromTrack [ 1 ] = 2 ;
iTakeFromTrack [ 2 ] = 1 ;
iTakeFromTrack [ 3 ] = 4 ;
iTakeFromTrack [ 4 ] = 3 ;
iTakeFromTrack [ 5 ] = 5 ;
break ;
default :
break ;
}
break ;
2011-03-17 01:47:30 -04:00
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 < inout . GetNumTracks (); t1 ++ )
{
const TapNote & tn1 = inout . GetTapNote ( t1 , r );
switch ( tn1 . type )
{
2014-07-25 18:45:02 -05:00
case TapNoteType_Empty :
case TapNoteType_HoldHead :
case TapNoteType_HoldTail :
case TapNoteType_AutoKeysound :
2011-03-17 01:47:30 -04:00
continue ; // skip
2014-07-25 18:45:02 -05:00
case TapNoteType_Tap :
case TapNoteType_Mine :
case TapNoteType_Attack :
case TapNoteType_Lift :
case TapNoteType_Fake :
2011-03-17 01:47:30 -04:00
break ; // shuffle this
DEFAULT_FAIL ( tn1 . type );
}
DEBUG_ASSERT_M ( ! inout . IsHoldNoteAtRow ( t1 , r ), ssprintf ( "There is a tap.type = %d inside of a hold at row %d" , tn1 . type , r ) );
// Probe for a spot to swap with.
set < int > 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 )
{
2014-07-25 18:45:02 -05:00
case TapNoteType_HoldHead :
case TapNoteType_HoldTail :
case TapNoteType_AutoKeysound :
2011-03-17 01:47:30 -04:00
continue ; // don't swap with these
2014-07-25 18:45:02 -05:00
case TapNoteType_Empty :
case TapNoteType_Tap :
case TapNoteType_Mine :
case TapNoteType_Attack :
case TapNoteType_Lift :
case TapNoteType_Fake :
2011-03-17 01:47:30 -04:00
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 );
2014-07-23 05:34:23 -06:00
inout . RevalidateATIs ( vector < int > (), false );
2011-03-17 01:47:30 -04:00
}
void NoteDataUtil :: Backwards ( NoteData & inout )
{
NoteData out ;
out . SetNumTracks ( inout . GetNumTracks () );
int max_row = inout . GetLastRow ();
for ( int t = 0 ; t < inout . GetNumTracks (); t ++ )
{
FOREACH_NONEMPTY_ROW_IN_TRACK_RANGE ( inout , t , r , 0 , max_row )
{
int iRowEarlier = r ;
int iRowLater = max_row - r ;
const TapNote & tnEarlier = inout . GetTapNote ( t , iRowEarlier );
2014-07-25 18:45:02 -05:00
if ( tnEarlier . type == TapNoteType_HoldHead )
2011-03-17 01:47:30 -04:00
iRowLater -= tnEarlier . iDuration ;
out . SetTapNote ( t , iRowLater , tnEarlier );
}
}
inout . swap ( out );
2014-07-23 05:34:23 -06:00
inout . RevalidateATIs ( vector < int > (), false );
2011-03-17 01:47:30 -04:00
}
void NoteDataUtil :: SwapSides ( NoteData & inout )
{
int iOriginalTrackToTakeFrom [ MAX_NOTE_TRACKS ];
for ( int t = 0 ; t < inout . GetNumTracks () / 2 ; ++ t )
{
int iTrackEarlier = t ;
int iTrackLater = t + inout . GetNumTracks () / 2 + inout . GetNumTracks () % 2 ;
iOriginalTrackToTakeFrom [ iTrackEarlier ] = iTrackLater ;
iOriginalTrackToTakeFrom [ iTrackLater ] = iTrackEarlier ;
}
NoteData orig ( inout );
inout . LoadTransformed ( orig , orig . GetNumTracks (), iOriginalTrackToTakeFrom );
2014-07-23 05:34:23 -06:00
inout . RevalidateATIs ( vector < int > (), false );
2011-03-17 01:47:30 -04:00
}
void NoteDataUtil :: Little ( NoteData & inout , int iStartIndex , int iEndIndex )
{
// filter out all non-quarter notes
for ( int t = 0 ; t < inout . GetNumTracks (); t ++ )
{
FOREACH_NONEMPTY_ROW_IN_TRACK_RANGE ( inout , t , i , iStartIndex , iEndIndex )
{
if ( i % ROWS_PER_BEAT == 0 )
continue ;
inout . SetTapNote ( t , i , TAP_EMPTY );
}
}
2014-07-23 05:34:23 -06:00
inout . RevalidateATIs ( vector < int > (), false );
2011-03-17 01:47:30 -04:00
}
// Make all quarter notes into jumps.
void NoteDataUtil :: Wide ( NoteData & inout , int iStartIndex , int iEndIndex )
{
/* Start on an even beat. */
iStartIndex = Quantize ( iStartIndex , BeatToNoteRow ( 2.0f ) );
FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE ( inout , i , iStartIndex , iEndIndex )
{
if ( i % BeatToNoteRow ( 2.0f ) != 0 )
continue ; // even beats only
bool bHoldNoteAtBeat = false ;
for ( int t = 0 ; ! bHoldNoteAtBeat && t < inout . GetNumTracks (); ++ t )
if ( inout . IsHoldNoteAtRow ( t , i ) )
bHoldNoteAtBeat = true ;
if ( bHoldNoteAtBeat )
continue ; // skip. Don't place during holds
if ( inout . GetNumTracksWithTap ( i ) != 1 )
continue ; // skip
bool bSpaceAroundIsEmpty = true ; // no other notes with a 1/8th of this row
FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE ( inout , j , i - ROWS_PER_BEAT / 2 + 1 , i + ROWS_PER_BEAT / 2 )
if ( j != i && inout . GetNumTapNonEmptyTracks ( j ) > 0 )
{
bSpaceAroundIsEmpty = false ;
break ;
}
if ( ! bSpaceAroundIsEmpty )
continue ; // skip
// add a note determinitsitcally
int iBeat = lrintf ( 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 );
2014-07-25 18:45:02 -05:00
if ( inout . GetTapNote ( iTrackToAdd , i ). type != TapNoteType_Empty && inout . GetTapNote ( iTrackToAdd , i ). type != TapNoteType_Fake )
2011-03-17 01:47:30 -04:00
{
iTrackToAdd = ( iTrackToAdd + 1 ) % inout . GetNumTracks ();
}
inout . SetTapNote ( iTrackToAdd , i , TAP_ADDITION_TAP );
}
2014-07-23 05:34:23 -06:00
inout . RevalidateATIs ( vector < int > (), false );
2011-03-17 01:47:30 -04:00
}
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 ;
2013-01-26 00:05:46 -05:00
if ( bSkippy &&
iTrackOfNoteEarlier != iTrackOfNoteLater && // Don't make skips on the same note
bEarlierHasNonEmptyTrack )
{
iTrackOfNoteToAdd = iTrackOfNoteEarlier ;
}
else if ( abs ( iTrackOfNoteEarlier - iTrackOfNoteLater ) >= 2 )
2011-03-17 01:47:30 -04:00
{
2013-01-24 23:33:42 -05:00
// try to choose a track between the earlier and later notes
2013-01-26 00:05:46 -05:00
iTrackOfNoteToAdd = min ( iTrackOfNoteEarlier , iTrackOfNoteLater ) + 1 ;
}
else if ( min ( iTrackOfNoteEarlier , iTrackOfNoteLater ) - 1 >= 0 )
{
2013-01-24 23:33:42 -05:00
// try to choose a track just to the left
2013-01-26 00:05:46 -05:00
iTrackOfNoteToAdd = min ( iTrackOfNoteEarlier , iTrackOfNoteLater ) - 1 ;
}
else if ( max ( iTrackOfNoteEarlier , iTrackOfNoteLater ) + 1 < inout . GetNumTracks () )
{
2013-01-24 23:33:42 -05:00
// try to choose a track just to the right
2013-01-26 00:05:46 -05:00
iTrackOfNoteToAdd = max ( iTrackOfNoteEarlier , iTrackOfNoteLater ) + 1 ;
2011-03-17 01:47:30 -04:00
}
inout . SetTapNote ( iTrackOfNoteToAdd , iRowToAdd , TAP_ADDITION_TAP );
}
2014-07-23 05:34:23 -06:00
inout . RevalidateATIs ( vector < int > (), false );
2011-03-17 01:47:30 -04:00
}
#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 == XXX )
;
}
}
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 < inout . GetNumTracks (); t ++ )
2014-07-25 18:45:02 -05:00
if ( inout . GetTapNote ( t , r ). type == TapNoteType_Tap )
2011-03-17 01:47:30 -04:00
inout . SetTapNote ( t , r , TAP_ADDITION_MINE );
iRowCount = 0 ;
if ( iPlaceEveryRows == 6 )
iPlaceEveryRows = 7 ;
else
iPlaceEveryRows = 6 ;
}
}
// Place mines right after hold so player must lift their foot.
for ( int iTrack = 0 ; iTrack < inout . GetNumTracks (); ++ iTrack )
{
FOREACH_NONEMPTY_ROW_IN_TRACK_RANGE ( inout , iTrack , r , iStartIndex , iEndIndex )
{
const TapNote & tn = inout . GetTapNote ( iTrack , r );
2014-07-25 18:45:02 -05:00
if ( tn . type != TapNoteType_HoldHead )
2011-03-17 01:47:30 -04:00
continue ;
int iMineRow = r + tn . iDuration + BeatToNoteRow ( 0.5f );
if ( iMineRow < iStartIndex || iMineRow > 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 < inout . GetNumTracks (); t ++ )
2014-07-25 18:45:02 -05:00
if ( inout . GetTapNote ( t , iMineRow ). type == TapNoteType_Tap )
2011-03-17 01:47:30 -04:00
inout . SetTapNote ( t , iMineRow , TAP_ADDITION_MINE );
iRowCount = 0 ;
}
}
2014-07-23 05:34:23 -06:00
inout . RevalidateATIs ( vector < int > (), false );
2011-03-17 01:47:30 -04:00
}
void NoteDataUtil :: Echo ( NoteData & inout , int iStartIndex , int iEndIndex )
{
// add 8th note tap "echos" after all taps
int iEchoTrack = - 1 ;
const int rows_per_interval = BeatToNoteRow ( 0.5f );
iStartIndex = Quantize ( iStartIndex , rows_per_interval );
/* Clamp iEndIndex to the last real tap note. Otherwise, we'll keep adding
* echos of our echos all the way up to MAX_TAP_ROW. */
iEndIndex = min ( iEndIndex , inout . GetLastRow () ) + 1 ;
// window is one beat wide and slides 1/2 a beat at a time
FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE ( inout , r , iStartIndex , iEndIndex )
{
if ( r % rows_per_interval != 0 )
continue ; // 8th notes only
const int iRowWindowBegin = r ;
const int iRowWindowEnd = r + rows_per_interval * 2 ;
const int iFirstTapInRow = inout . GetFirstTrackWithTap ( iRowWindowBegin );
if ( iFirstTapInRow != - 1 )
iEchoTrack = iFirstTapInRow ;
if ( iEchoTrack ==- 1 )
continue ; // don't lay
// don't insert a new note if there's already a tap within this interval
bool bTapInMiddle = false ;
FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE ( inout , r2 , iRowWindowBegin + 1 , iRowWindowEnd - 1 )
bTapInMiddle = true ;
if ( bTapInMiddle )
continue ; // don't lay
const int iRowEcho = r + rows_per_interval ;
{
set < int > 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 );
}
2014-07-23 05:34:23 -06:00
inout . RevalidateATIs ( vector < int > (), false );
2011-03-17 01:47:30 -04:00
}
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 < inout . GetNumTracks (); t ++ )
{
if ( iTrackAddedThisRow > iSimultaneousHolds )
break ;
2014-07-25 18:45:02 -05:00
if ( inout . GetTapNote ( t , r ). type == TapNoteType_Tap )
2011-03-17 01:47:30 -04:00
{
// Find the ending row for this hold
int iTapsLeft = iSimultaneousHolds ;
int r2 = r + 1 ;
2013-01-24 23:29:54 -05:00
bool addHold = true ;
2011-03-17 01:47:30 -04:00
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.
2014-07-25 18:45:02 -05:00
if ( inout . GetTapNote ( t , r2 ). type != TapNoteType_Empty )
2013-01-24 23:29:54 -05:00
{
addHold = false ;
break ;
}
2011-03-17 01:47:30 -04:00
set < int > 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 )
2013-01-24 23:29:54 -05:00
{
addHold = false ;
break ;
}
}
if ( ! addHold )
{
continue ;
2011-03-17 01:47:30 -04:00
}
// 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 ++ ;
}
}
}
2014-07-23 05:34:23 -06:00
inout . RevalidateATIs ( vector < int > (), false );
2011-03-17 01:47:30 -04:00
}
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 < inout . GetNumTracks (); t ++ )
{
2014-07-25 18:45:02 -05:00
if ( inout . GetTapNote ( t , r ). type == TapNoteType_Tap ) // there is a tap here
2011-03-17 01:47:30 -04:00
{
// Look to see if there is enough empty space on either side of the note
// to turn this into a jump.
int iRowWindowBegin = r - BeatToNoteRow ( 0.5f );
int iRowWindowEnd = r + BeatToNoteRow ( 0.5f );
bool bTapInMiddle = false ;
FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE ( inout , r2 , iRowWindowBegin + 1 , iRowWindowEnd - 1 )
if ( inout . IsThereATapAtRow ( r2 ) && r2 != r ) // don't count the note we're looking around
{
bTapInMiddle = true ;
break ;
}
if ( bTapInMiddle )
continue ;
// don't convert to jump if there's a hold here
int iNumTracksHeld = inout . GetNumTracksHeldAtRow ( r );
if ( iNumTracksHeld >= 1 )
continue ;
int iOppositeTrack = iTrackMapping [ t ];
inout . SetTapNote ( iOppositeTrack , r , TAP_ADDITION_TAP );
}
}
}
2014-07-23 05:34:23 -06:00
inout . RevalidateATIs ( vector < int > (), false );
2011-03-17 01:47:30 -04:00
}
void NoteDataUtil :: SnapToNearestNoteType ( NoteData & inout , NoteType nt1 , NoteType nt2 , int iStartIndex , int iEndIndex )
{
// nt2 is optional and should be NoteType_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 != NoteType_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 ; c < inout . GetNumTracks (); c ++ )
{
TapNote tnNew = inout . GetTapNote ( c , iOldIndex );
2014-07-25 18:45:02 -05:00
if ( tnNew . type == TapNoteType_Empty )
2011-03-17 01:47:30 -04:00
continue ;
inout . SetTapNote ( c , iOldIndex , TAP_EMPTY );
2014-07-25 18:45:02 -05:00
if ( tnNew . type == TapNoteType_Tap && inout . IsHoldNoteAtRow ( c , iNewIndex ) )
2011-03-17 01:47:30 -04:00
continue ; // HoldNotes override TapNotes
2014-07-25 18:45:02 -05:00
if ( tnNew . type == TapNoteType_HoldHead )
2011-03-17 01:47:30 -04:00
{
/* Quantize the duration. If the result is empty, just discard the hold. */
tnNew . iDuration = Quantize ( tnNew . iDuration , BeatToNoteRow ( fSnapInterval1 ) );
if ( tnNew . iDuration == 0 )
continue ;
/* We might be moving a hold note downwards, or extending its duration
* downwards. Make sure there isn't anything else in the new range. */
inout . ClearRangeForTrack ( iNewIndex , iNewIndex + tnNew . iDuration + 1 , c );
}
inout . SetTapNote ( c , iNewIndex , tnNew );
}
}
2014-07-23 05:34:23 -06:00
inout . RevalidateATIs ( vector < int > (), false );
2011-03-17 01:47:30 -04:00
}
void NoteDataUtil :: CopyLeftToRight ( NoteData & inout )
{
/* XXX
inout.ConvertHoldNotesTo4s();
for( int t=0; t<inout.GetNumTracks()/2; t++ )
{
FOREACH_NONEMPTY_ROW_IN_TRACK( inout, t, r )
{
int iTrackEarlier = t;
int iTrackLater = inout.GetNumTracks()-1-t;
const TapNote &tnEarlier = inout.GetTapNote(iTrackEarlier, r);
inout.SetTapNote(iTrackLater, r, tnEarlier);
}
}
inout.Convert4sToHoldNotes();
*/
}
void NoteDataUtil :: CopyRightToLeft ( NoteData & inout )
{
/* XXX
inout.ConvertHoldNotesTo4s();
for( int t=0; t<inout.GetNumTracks()/2; t++ )
{
FOREACH_NONEMPTY_ROW_IN_TRACK( inout, t, r )
{
int iTrackEarlier = t;
int iTrackLater = inout.GetNumTracks()-1-t;
TapNote tnLater = inout.GetTapNote(iTrackLater, r);
inout.SetTapNote(iTrackEarlier, r, tnLater);
}
}
inout.Convert4sToHoldNotes();
*/
}
void NoteDataUtil :: ClearLeft ( NoteData & inout )
{
for ( int t = 0 ; t < inout . GetNumTracks () / 2 ; t ++ )
inout . ClearRangeForTrack ( 0 , MAX_NOTE_ROW , t );
}
void NoteDataUtil :: ClearRight ( NoteData & inout )
{
for ( int t = ( inout . GetNumTracks () + 1 ) / 2 ; t < inout . GetNumTracks (); t ++ )
inout . ClearRangeForTrack ( 0 , MAX_NOTE_ROW , t );
}
void NoteDataUtil :: CollapseToOne ( NoteData & inout )
{
FOREACH_NONEMPTY_ROW_ALL_TRACKS ( inout , r )
for ( int t = 1 ; t < inout . GetNumTracks (); t ++ )
{
NoteData :: iterator iter = inout . FindTapNote ( t , r );
if ( iter == inout . end ( t ) )
continue ;
inout . SetTapNote ( 0 , r , iter -> second );
inout . RemoveTapNote ( t , iter );
}
}
void NoteDataUtil :: CollapseLeft ( NoteData & inout )
{
FOREACH_NONEMPTY_ROW_ALL_TRACKS ( inout , r )
{
int iNumTracksFilled = 0 ;
for ( int t = 0 ; t < inout . GetNumTracks (); t ++ )
{
2014-07-25 18:45:02 -05:00
if ( inout . GetTapNote ( t , r ). type != TapNoteType_Empty )
2011-03-17 01:47:30 -04:00
{
TapNote tn = inout . GetTapNote ( t , r );
inout . SetTapNote ( t , r , TAP_EMPTY );
if ( iNumTracksFilled < inout . GetNumTracks () )
{
inout . SetTapNote ( iNumTracksFilled , r , tn );
++ iNumTracksFilled ;
}
}
}
}
}
void NoteDataUtil :: ShiftTracks ( NoteData & inout , int iShiftBy )
{
int iOriginalTrackToTakeFrom [ MAX_NOTE_TRACKS ];
for ( int i = 0 ; i < inout . GetNumTracks (); ++ i )
{
int iFrom = i - iShiftBy ;
wrap ( iFrom , inout . GetNumTracks () );
iOriginalTrackToTakeFrom [ i ] = iFrom ;
}
NoteData orig ( inout );
inout . LoadTransformed ( orig , orig . GetNumTracks (), iOriginalTrackToTakeFrom );
}
void NoteDataUtil :: ShiftLeft ( NoteData & inout )
{
ShiftTracks ( inout , - 1 );
}
void NoteDataUtil :: ShiftRight ( NoteData & inout )
{
ShiftTracks ( inout , + 1 );
}
2014-08-30 00:47:07 -06:00
void NoteDataUtil :: SwapUpDown ( NoteData & inout , StepsType st )
{
int TakeFrom [ MAX_NOTE_TRACKS ];
GetTrackMapping ( st , NoteDataUtil :: swap_up_down , inout . GetNumTracks (), TakeFrom );
NoteData tempND ;
tempND . LoadTransformed ( inout , inout . GetNumTracks (), TakeFrom );
inout . CopyAll ( tempND );
inout . RevalidateATIs ( vector < int > (), false );
}
2014-08-30 13:38:16 -06:00
void NoteDataUtil :: ArbitraryRemap ( NoteData & inout , int * mapping )
{
NoteData tempND ;
tempND . LoadTransformed ( inout , inout . GetNumTracks (), mapping );
inout . CopyAll ( tempND );
inout . RevalidateATIs ( vector < int > (), false );
}
2011-03-17 01:47:30 -04:00
struct ValidRow
{
StepsType st ;
bool bValidMask [ MAX_NOTE_TRACKS ];
};
#define T true
#define f false
const ValidRow g_ValidRows [] =
{
{ StepsType_dance_double , { T , T , T , T , f , f , f , f } },
{ StepsType_dance_double , { f , T , T , T , T , f , f , f } },
{ StepsType_dance_double , { f , f , f , T , T , T , T , f } },
{ StepsType_dance_double , { f , f , f , f , T , T , T , T } },
{ StepsType_pump_double , { T , T , T , T , T , f , f , f , f , f } },
{ StepsType_pump_double , { f , f , T , T , T , T , T , T , f , f } },
{ StepsType_pump_double , { f , f , f , f , f , T , T , T , T , T } },
};
#undef T
#undef f
void NoteDataUtil :: RemoveStretch ( NoteData & inout , StepsType st , int iStartIndex , int iEndIndex )
{
vector < const ValidRow *> vpValidRowsToCheck ;
for ( unsigned i = 0 ; i < ARRAYLEN ( g_ValidRows ); i ++ )
{
if ( g_ValidRows [ i ]. st == st )
vpValidRowsToCheck . push_back ( & g_ValidRows [ i ] );
}
// bail early if there's nothing to validate against
if ( vpValidRowsToCheck . empty () )
return ;
// each row must pass at least one valid mask
FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE ( inout , r , iStartIndex , iEndIndex )
{
// only check rows with jumps
if ( inout . GetNumTapNonEmptyTracks ( r ) < 2 )
continue ;
bool bPassedOneMask = false ;
for ( unsigned i = 0 ; i < vpValidRowsToCheck . size (); i ++ )
{
const ValidRow & vr = * vpValidRowsToCheck [ i ];
if ( NoteDataUtil :: RowPassesValidMask ( inout , r , vr . bValidMask ) )
{
bPassedOneMask = true ;
break ;
}
}
if ( ! bPassedOneMask )
RemoveAllButOneTap ( inout , r );
}
2014-07-23 05:34:23 -06:00
inout . RevalidateATIs ( vector < int > (), false );
2011-03-17 01:47:30 -04:00
}
bool NoteDataUtil :: RowPassesValidMask ( NoteData & inout , int row , const bool bValidMask [] )
{
for ( int t = 0 ; t < inout . GetNumTracks (); t ++ )
{
2014-07-25 18:45:02 -05:00
if ( ! bValidMask [ t ] && inout . GetTapNote ( t , row ). type != TapNoteType_Empty )
2011-03-17 01:47:30 -04:00
return false ;
}
return true ;
}
void NoteDataUtil :: ConvertAdditionsToRegular ( NoteData & inout )
{
for ( int t = 0 ; t < inout . GetNumTracks (); t ++ )
FOREACH_NONEMPTY_ROW_IN_TRACK ( inout , t , r )
2014-07-25 18:45:02 -05:00
if ( inout . GetTapNote ( t , r ). source == TapNoteSource_Addition )
2011-03-17 01:47:30 -04:00
{
TapNote tn = inout . GetTapNote ( t , r );
2014-07-25 18:45:02 -05:00
tn . source = TapNoteSource_Original ;
2011-03-17 01:47:30 -04:00
inout . SetTapNote ( t , r , tn );
}
2014-07-23 05:34:23 -06:00
inout . RevalidateATIs ( vector < int > (), false );
2011-03-17 01:47:30 -04:00
}
void NoteDataUtil :: TransformNoteData ( NoteData & nd , const AttackArray & aa , StepsType st , Song * pSong )
{
FOREACH_CONST ( Attack , aa , a )
{
PlayerOptions po ;
po . FromString ( a -> sModifiers );
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_NOLIFTS ] ) NoteDataUtil :: RemoveLifts ( nd , iStartIndex , iEndIndex );
if ( po . m_bTransforms [ PlayerOptions :: TRANSFORM_NOFAKES ] ) NoteDataUtil :: RemoveFakes ( 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 );
// Do this here to turn any added holds into rolls
if ( po . m_bTransforms [ PlayerOptions :: TRANSFORM_HOLDROLLS ] ) NoteDataUtil :: ChangeHoldsToRolls ( nd , iStartIndex , iEndIndex );
// Apply turns and shuffles last so that they affect inserts.
if ( po . m_bTurns [ PlayerOptions :: TURN_MIRROR ] ) NoteDataUtil :: Turn ( nd , st , NoteDataUtil :: mirror , iStartIndex , iEndIndex );
2011-10-16 15:34:29 -04:00
if ( po . m_bTurns [ PlayerOptions :: TURN_BACKWARDS ] ) NoteDataUtil :: Turn ( nd , st , NoteDataUtil :: backwards , iStartIndex , iEndIndex );
2011-03-17 01:47:30 -04:00
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_SOFT_SHUFFLE ] ) NoteDataUtil :: Turn ( nd , st , NoteDataUtil :: soft_shuffle , iStartIndex , iEndIndex );
if ( po . m_bTurns [ PlayerOptions :: TURN_SUPER_SHUFFLE ] ) NoteDataUtil :: Turn ( nd , st , NoteDataUtil :: super_shuffle , iStartIndex , iEndIndex );
2015-07-11 00:43:37 -06:00
nd . RevalidateATIs ( vector < int > (), false );
2011-03-17 01:47:30 -04:00
}
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 ; sec < pSong -> m_fMusicLengthSeconds ; sec += 30 )
{
2011-05-09 21:11:33 -04:00
float fBeat = pSong -> m_SongTiming . GetBeatFromElapsedTime ( sec );
2011-03-17 01:47:30 -04:00
int iBeat = ( int ) fBeat ;
int iTrack = iBeat % nd . GetNumTracks (); // deterministically calculates track
TapNote tn (
2014-07-25 18:45:02 -05:00
TapNoteType_Attack ,
TapNoteSubType_Invalid ,
TapNoteSource_Original ,
2011-03-17 01:47:30 -04:00
szAttacks [ RandomInt ( ARRAYLEN ( szAttacks ))],
15.0f ,
- 1 );
nd . SetTapNote ( iTrack , BeatToNoteRow ( fBeat ), tn );
}
2014-07-23 05:34:23 -06:00
nd . RevalidateATIs ( vector < int > (), false );
2011-03-17 01:47:30 -04:00
}
void NoteDataUtil :: Scale ( NoteData & nd , float fScale )
{
ASSERT ( fScale > 0 );
2011-03-19 19:38:45 +07:00
NoteData ndOut ;
ndOut . SetNumTracks ( nd . GetNumTracks () );
for ( int t = 0 ; t < nd . GetNumTracks (); t ++ )
2011-03-17 01:47:30 -04:00
{
2011-03-19 19:38:45 +07:00
for ( NoteData :: const_iterator iter = nd . begin ( t ); iter != nd . end ( t ); ++ iter )
2011-03-17 01:47:30 -04:00
{
2011-03-19 19:38:45 +07:00
TapNote tn = iter -> second ;
int iNewRow = lrintf ( fScale * iter -> first );
int iNewDuration = lrintf ( fScale * ( iter -> first + tn . iDuration ) );
tn . iDuration = iNewDuration ;
ndOut . SetTapNote ( t , iNewRow , tn );
2011-03-17 01:47:30 -04:00
}
}
2011-03-19 19:38:45 +07:00
nd . swap ( ndOut );
2014-07-23 05:34:23 -06:00
nd . RevalidateATIs ( vector < int > (), false );
2011-03-19 19:38:45 +07:00
}
/* XXX: move this to an appropriate place, same place as NoteRowToBeat perhaps? */
static inline int GetScaledRow ( float fScale , int iStartIndex , int iEndIndex , int iRow )
{
if ( iRow < iStartIndex )
return iRow ;
else if ( iRow > iEndIndex )
return iRow + lrintf ( ( iEndIndex - iStartIndex ) * ( fScale - 1 ) );
else
return lrintf ( ( iRow - iStartIndex ) * fScale ) + iStartIndex ;
2011-03-17 01:47:30 -04:00
}
void NoteDataUtil :: ScaleRegion ( NoteData & nd , float fScale , int iStartIndex , int iEndIndex )
{
ASSERT ( fScale > 0 );
ASSERT ( iStartIndex < iEndIndex );
ASSERT ( iStartIndex >= 0 );
2011-03-19 19:38:45 +07:00
NoteData ndOut ;
ndOut . SetNumTracks ( nd . GetNumTracks () );
for ( int t = 0 ; t < nd . GetNumTracks (); t ++ )
2011-03-17 01:47:30 -04:00
{
for ( NoteData :: const_iterator iter = nd . begin ( t ); iter != nd . end ( t ); ++ iter )
{
2011-03-19 19:38:45 +07:00
TapNote tn = iter -> second ;
int iNewRow = GetScaledRow ( fScale , iStartIndex , iEndIndex , iter -> first );
int iNewDuration = GetScaledRow ( fScale , iStartIndex , iEndIndex , iter -> first + tn . iDuration ) - iNewRow ;
tn . iDuration = iNewDuration ;
ndOut . SetTapNote ( t , iNewRow , tn );
2011-03-17 01:47:30 -04:00
}
}
2011-03-19 19:38:45 +07:00
nd . swap ( ndOut );
2014-07-23 05:34:23 -06:00
nd . RevalidateATIs ( vector < int > (), false );
2011-03-17 01:47:30 -04:00
}
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 );
2014-07-23 05:34:23 -06:00
nd . RevalidateATIs ( vector < int > (), false );
2011-03-17 01:47:30 -04:00
}
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 );
2014-07-23 05:34:23 -06:00
nd . RevalidateATIs ( vector < int > (), false );
2011-03-17 01:47:30 -04:00
}
2014-07-25 18:45:02 -05:00
void NoteDataUtil :: RemoveAllTapsOfType ( NoteData & ndInOut , TapNoteType typeToRemove )
2011-03-17 01:47:30 -04:00
{
/* 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 ; t < ndInOut . GetNumTracks (); t ++ )
{
for ( NoteData :: iterator iter = ndInOut . begin ( t ); iter != ndInOut . end ( t ); )
{
if ( iter -> second . type == typeToRemove )
ndInOut . RemoveTapNote ( t , iter ++ );
else
++ iter ;
}
}
2014-07-23 05:34:23 -06:00
ndInOut . RevalidateATIs ( vector < int > (), false );
2011-03-17 01:47:30 -04:00
}
2014-07-25 18:45:02 -05:00
void NoteDataUtil :: RemoveAllTapsExceptForType ( NoteData & ndInOut , TapNoteType typeToKeep )
2011-03-17 01:47:30 -04:00
{
/* Same as in RemoveAllTapsOfType(). */
for ( int t = 0 ; t < ndInOut . GetNumTracks (); t ++ )
{
for ( NoteData :: iterator iter = ndInOut . begin ( t ); iter != ndInOut . end ( t ); )
{
if ( iter -> second . type != typeToKeep )
ndInOut . RemoveTapNote ( t , iter ++ );
else
++ iter ;
}
}
2014-07-23 05:34:23 -06:00
ndInOut . RevalidateATIs ( vector < int > (), false );
2011-03-17 01:47:30 -04:00
}
int NoteDataUtil :: GetMaxNonEmptyTrack ( const NoteData & in )
{
for ( int t = in . GetNumTracks () - 1 ; t >= 0 ; t -- )
if ( ! in . IsTrackEmpty ( t ) )
return t ;
return - 1 ;
}
bool NoteDataUtil :: AnyTapsAndHoldsInTrackRange ( const NoteData & in , int iTrack , int iStart , int iEnd )
{
if ( iStart >= 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 )
{
2014-07-25 18:45:02 -05:00
case TapNoteType_Empty :
case TapNoteType_Mine :
2011-03-17 01:47:30 -04:00
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 < in . GetNumTracks (); t ++ )
{
int iHeadRow ;
if ( ! in . IsHoldHeadOrBodyAtRow ( t , iOriginalRow , & iHeadRow ) )
continue ;
const TapNote & tn = in . GetTapNote ( t , iHeadRow );
int iEndRow = iHeadRow + tn . iDuration ;
if ( iEndRow == iOriginalRow )
continue ;
bAnyHaveNextNote = true ;
ASSERT ( iEndRow < MAX_NOTE_ROW );
iClosestNextRow = min ( iClosestNextRow , iEndRow );
}
if ( ! bAnyHaveNextNote )
return false ;
rowInOut = iClosestNextRow ;
return true ;
}
bool NoteDataUtil :: GetPrevEditorPosition ( const NoteData & in , int & rowInOut )
{
int iOriginalRow = rowInOut ;
bool bAnyHavePrevNote = in . GetPrevTapNoteRowForAllTracks ( rowInOut );
int iClosestPrevRow = rowInOut ;
for ( int t = 0 ; t < in . GetNumTracks (); t ++ )
{
int iHeadRow = iOriginalRow ;
if ( ! in . GetPrevTapNoteRowForTrack ( t , iHeadRow ) )
continue ;
const TapNote & tn = in . GetTapNote ( t , iHeadRow );
2014-07-25 18:45:02 -05:00
if ( tn . type != TapNoteType_HoldHead )
2011-03-17 01:47:30 -04:00
continue ;
int iEndRow = iHeadRow + tn . iDuration ;
if ( iEndRow >= iOriginalRow )
continue ;
bAnyHavePrevNote = true ;
ASSERT ( iEndRow < MAX_NOTE_ROW );
iClosestPrevRow = max ( iClosestPrevRow , iEndRow );
}
if ( ! bAnyHavePrevNote )
return false ;
rowInOut = iClosestPrevRow ;
return true ;
}
2013-12-19 23:46:01 -06:00
unsigned int NoteDataUtil :: GetTotalHoldTicks ( NoteData * nd , const TimingData * td )
{
unsigned int ret = 0 ;
2014-04-23 21:45:37 -05:00
// Last row must be included. -- Matt
int end = nd -> GetLastRow () + 1 ;
2013-12-19 23:46:01 -06:00
vector < TimingSegment *> segments = td -> GetTimingSegments ( SEGMENT_TICKCOUNT );
// We start with the LAST TimingSegment and work our way backwards.
// This way we can continually update end instead of having to lookup when
// the next segment starts.
for ( int i = segments . size () - 1 ; i >= 0 ; i -- )
{
TickcountSegment * ts = ( TickcountSegment * ) segments [ i ];
if ( ts -> GetTicks () > 0 )
{
// Jump to each point where holds would tick and add the number of holds there to ret.
for ( int j = ts -> GetRow (); j < end ; j += ROWS_PER_BEAT / ts -> GetTicks () )
2014-04-23 21:45:37 -05:00
// 1 tick per row.
if ( nd -> GetNumTracksHeldAtRow ( j ) > 0 )
ret ++ ;
2013-12-19 23:46:01 -06:00
}
end = ts -> GetRow ();
}
return ret ;
}
2011-03-17 01:47:30 -04:00
/*
* (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.
*/