Files
itgmania212121/stepmania/src/NoteDataUtil.cpp
T

1773 lines
52 KiB
C++
Raw Normal View History

2003-08-07 06:36:34 +00:00
#include "global.h"
#include "NoteDataUtil.h"
#include "RageUtil.h"
#include "RageLog.h"
2003-08-13 19:17:28 +00:00
#include "PlayerOptions.h"
2003-11-20 16:17:34 +00:00
#include "song.h"
#include "GameState.h"
2004-07-11 07:21:33 +00:00
#include "RadarValues.h"
#include "Foreach.h"
2003-08-07 06:36:34 +00:00
NoteType NoteDataUtil::GetSmallestNoteTypeForMeasure( const NoteData &n, int iMeasureIndex )
{
const int iMeasureStartIndex = iMeasureIndex * ROWS_PER_MEASURE;
const int iMeasureLastIndex = (iMeasureIndex+1) * ROWS_PER_MEASURE - 1;
// probe to find the smallest note type
NoteType nt;
for( nt=(NoteType)0; nt<NUM_NOTE_TYPES; nt=NoteType(nt+1) ) // for each NoteType, largest to largest
{
float fBeatSpacing = NoteTypeToBeat( nt );
int iRowSpacing = int(roundf( fBeatSpacing * ROWS_PER_BEAT ));
bool bFoundSmallerNote = false;
for( int i=iMeasureStartIndex; i<=iMeasureLastIndex; i++ ) // for each index in this measure
{
if( i % iRowSpacing == 0 )
continue; // skip
if( !n.IsRowEmpty(i) )
{
bFoundSmallerNote = true;
break;
}
}
if( bFoundSmallerNote )
continue; // searching the next NoteType
else
break; // stop searching. We found the smallest NoteType
}
if( nt == NUM_NOTE_TYPES ) // we didn't find one
return NOTE_TYPE_INVALID; // well-formed notes created in the editor should never get here
else
return nt;
}
2004-10-23 23:41:49 +00:00
void NoteDataUtil::LoadFromSMNoteDataString( NoteData &out, CString sSMNoteData )
2003-08-07 06:36:34 +00:00
{
2004-10-23 23:41:49 +00:00
//
// Load note data
//
/* Clear notes, but keep the same number of tracks. */
int iNumTracks = out.GetNumTracks();
out.Init();
out.SetNumTracks( iNumTracks );
// strip comments out of sSMNoteData
while( sSMNoteData.Find("//") != -1 )
2003-08-07 06:36:34 +00:00
{
2004-10-23 23:41:49 +00:00
int iIndexCommentStart = sSMNoteData.Find("//");
int iIndexCommentEnd = sSMNoteData.Find("\n", iIndexCommentStart);
if( iIndexCommentEnd == -1 ) // comment doesn't have an end?
sSMNoteData.erase( iIndexCommentStart, 2 );
else
sSMNoteData.erase( iIndexCommentStart, iIndexCommentEnd-iIndexCommentStart );
}
2003-08-07 06:36:34 +00:00
2004-10-23 23:41:49 +00:00
CStringArray asMeasures;
split( sSMNoteData, ",", asMeasures, true ); // ignore empty is important
for( unsigned m=0; m<asMeasures.size(); m++ ) // foreach measure
{
CString &sMeasureString = asMeasures[m];
TrimLeft(sMeasureString);
TrimRight(sMeasureString);
2003-08-07 06:36:34 +00:00
2004-10-23 23:41:49 +00:00
CStringArray asMeasureLines;
split( sMeasureString, "\n", asMeasureLines, true ); // ignore empty is important
2003-08-07 06:36:34 +00:00
2004-10-23 23:41:49 +00:00
for( unsigned l=0; l<asMeasureLines.size(); l++ )
2003-08-07 06:36:34 +00:00
{
2004-10-23 23:41:49 +00:00
CString &sMeasureLine = asMeasureLines[l];
TrimLeft(sMeasureLine);
TrimRight(sMeasureLine);
2003-11-17 03:38:24 +00:00
2004-10-23 23:41:49 +00:00
const float fPercentIntoMeasure = l/(float)asMeasureLines.size();
const float fBeat = (m + fPercentIntoMeasure) * BEATS_PER_MEASURE;
const int iIndex = BeatToNoteRow( fBeat );
2003-08-07 06:36:34 +00:00
2004-10-23 23:41:49 +00:00
// if( m_iNumTracks != sMeasureLine.GetLength() )
// RageException::Throw( "Actual number of note columns (%d) is different from the StepsType (%d).", m_iNumTracks, sMeasureLine.GetLength() );
2003-11-17 03:38:24 +00:00
2004-10-23 23:41:49 +00:00
const char *p = sMeasureLine;
int iTrack = 0;
while( iTrack < iNumTracks && *p )
2004-10-23 23:41:49 +00:00
{
TapNote tn;
char ch = *p;
switch( ch )
{
2004-10-24 10:20:24 +00:00
case '0': tn = TAP_EMPTY; break;
case '1': tn = TAP_ORIGINAL_TAP; break;
case '2': tn = TAP_ORIGINAL_HOLD_HEAD; break;
case '3': tn = TAP_ORIGINAL_HOLD_TAIL; break;
2004-10-23 23:41:49 +00:00
// case 'm':
// Don't be loose with the definition. Use only 'M' since
// that's what we've been writing to disk. -Chris
2004-10-24 10:20:24 +00:00
case 'M': tn = TAP_ORIGINAL_MINE; break;
case 'A': tn = TAP_ORIGINAL_ATTACK; break;
2004-10-24 10:45:30 +00:00
case 'K': tn = TAP_ORIGINAL_AUTO_KEYSOUND; break;
2004-10-23 23:41:49 +00:00
default:
/* Invalid data. We don't want to assert, since there might
* simply be invalid data in an .SM, and we don't want to die
* due to invalid data. We should probably check for this when
* we load SM data for the first time ... */
// ASSERT(0);
tn = TAP_EMPTY;
break;
}
p++;
// look for optional attack info (e.g. "{tipsy,50% drunk:15.2}")
if( *p == '{' )
{
p++;
2003-11-17 03:38:24 +00:00
2004-10-23 23:41:49 +00:00
char szModifiers[256] = "";
float fDurationSeconds = 0;
2005-01-09 22:28:31 +00:00
if( sscanf( p, "%255[^:]:%f}", szModifiers, &fDurationSeconds ) == 2 ) // not fatal if this fails due to malformed data
2004-10-23 23:41:49 +00:00
{
tn.type = TapNote::attack;
tn.sAttackModifiers = szModifiers;
tn.fAttackDurationSeconds = fDurationSeconds;
}
2003-11-17 03:38:24 +00:00
2004-10-23 23:41:49 +00:00
// skip past the '}'
while( *p )
2003-11-17 03:38:24 +00:00
{
2004-10-23 23:41:49 +00:00
if( *p == '}' )
2003-11-17 03:38:24 +00:00
{
2004-10-23 23:41:49 +00:00
p++;
break;
2003-11-17 03:38:24 +00:00
}
2004-10-23 23:41:49 +00:00
p++;
2003-11-17 03:38:24 +00:00
}
2004-10-23 23:41:49 +00:00
}
// look for optional keysound index (e.g. "[123]")
if( *p == '[' )
{
p++;
2004-10-23 23:41:49 +00:00
int iKeysoundIndex = 0;
if( 1 == sscanf( p, "%d]", &iKeysoundIndex ) ) // not fatal if this fails due to malformed data
{
2004-10-23 23:41:49 +00:00
tn.bKeysound = true;
tn.iKeysoundIndex = iKeysoundIndex;
}
2004-10-23 23:41:49 +00:00
// skip past the ']'
while( *p )
{
if( *p == ']' )
{
p++;
2004-10-23 23:41:49 +00:00
break;
}
2004-10-23 23:41:49 +00:00
p++;
}
2003-08-07 06:36:34 +00:00
}
2005-01-03 20:54:28 +00:00
/* Optimization: if we pass TAP_EMPTY, NoteData will do a search
* to remove anything in this position. We know that there's nothing
* there, so avoid the search. */
if( tn.type != TapNote::empty )
out.SetTapNote( iTrack, iIndex, tn );
2003-11-17 03:38:24 +00:00
2004-10-23 23:41:49 +00:00
iTrack++;
}
2003-11-17 03:38:24 +00:00
}
}
2004-10-23 23:41:49 +00:00
out.Convert2sAnd3sToHoldNotes();
2003-11-17 03:38:24 +00:00
}
2004-10-23 23:41:49 +00:00
void NoteDataUtil::GetSMNoteDataString( const NoteData &in_, CString &notes_out )
2003-11-17 03:38:24 +00:00
{
2004-10-23 23:41:49 +00:00
//
// Get note data
//
NoteData in;
in.To2sAnd3s( in_ );
2003-08-07 06:36:34 +00:00
2004-10-23 23:41:49 +00:00
float fLastBeat = in.GetLastBeat();
int iLastMeasure = int( fLastBeat/BEATS_PER_MEASURE );
2003-08-07 06:36:34 +00:00
2004-10-23 23:41:49 +00:00
CString &sRet = notes_out;
2003-08-07 06:36:34 +00:00
2004-10-23 23:41:49 +00:00
sRet = "";
for( int m=0; m<=iLastMeasure; m++ ) // foreach measure
{
if( m )
sRet.append( 1, ',' );
2004-10-23 23:41:49 +00:00
NoteType nt = GetSmallestNoteTypeForMeasure( in, m );
int iRowSpacing;
if( nt == NOTE_TYPE_INVALID )
iRowSpacing = 1;
else
iRowSpacing = int(roundf( NoteTypeToBeat(nt) * ROWS_PER_BEAT ));
2003-11-17 03:38:24 +00:00
2004-10-23 23:41:49 +00:00
sRet += ssprintf(" // measure %d\n", m+1);
2003-11-17 03:38:24 +00:00
2004-10-23 23:41:49 +00:00
const int iMeasureStartRow = m * ROWS_PER_MEASURE;
const int iMeasureLastRow = (m+1) * ROWS_PER_MEASURE - 1;
2003-11-17 03:38:24 +00:00
2004-10-23 23:41:49 +00:00
for( int r=iMeasureStartRow; r<=iMeasureLastRow; r+=iRowSpacing )
{
for( int t=0; t<in.GetNumTracks(); t++ )
2003-11-12 08:13:02 +00:00
{
2004-10-24 10:20:24 +00:00
const TapNote &tn = in.GetTapNote(t, r);
2004-10-23 23:41:49 +00:00
char c;
switch( tn.type )
2003-11-12 08:13:02 +00:00
{
2004-10-23 23:41:49 +00:00
case TapNote::empty: c = '0'; break;
case TapNote::tap: c = '1'; break;
case TapNote::hold_head: c = '2'; break;
case TapNote::hold_tail: c = '3'; break;
case TapNote::mine: c = 'M'; break;
case TapNote::attack: c = 'A'; break;
2004-10-24 10:45:30 +00:00
case TapNote::autoKeysound: c = 'K'; break;
2004-10-23 23:41:49 +00:00
default:
2004-10-25 02:17:58 +00:00
FAIL_M( ssprintf("tn %i", tn.type) ); // invalid enum value
2004-10-23 23:41:49 +00:00
}
sRet.append(1, c);
2004-10-23 23:41:49 +00:00
if( tn.type == TapNote::attack )
{
sRet.append( ssprintf("{%s:%.2f}",tn.sAttackModifiers.c_str(), tn.fAttackDurationSeconds) );
}
if( tn.bKeysound )
{
sRet.append( ssprintf("[%d]",tn.iKeysoundIndex) );
2003-08-07 06:36:34 +00:00
}
}
2004-10-23 23:41:49 +00:00
sRet.append(1, '\n');
2003-11-17 03:38:24 +00:00
}
}
2003-08-07 06:36:34 +00:00
}
2004-07-17 05:10:11 +00: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;
}
NoteData Original;
Original.To4s( in );
out.Config( in );
out.SetNumTracks( iNewNumTracks );
int iCurTrackOffset = 0;
int iTrackOffsetMin = 0;
int iTrackOffsetMax = abs( iNewNumTracks - Original.GetNumTracks() );
int bOffsetIncreasing = true;
int iLastRow = Original.GetLastRow();
int iLastMeasure = 0;
2004-07-17 05:10:11 +00:00
for( int r=0; r<=iLastRow; )
{
// copy notes in this measure
for( int t=0; t<Original.GetNumTracks(); t++ )
{
int iOldTrack = t;
int iNewTrack = (iOldTrack + iCurTrackOffset) % iNewNumTracks;
2004-10-24 10:20:24 +00:00
const TapNote &tn = Original.GetTapNote( iOldTrack, r );
out.SetTapNote( iNewTrack, r, tn );
2004-07-17 05:10:11 +00:00
}
r++;
const int iMeasure = r / ROWS_PER_MEASURE;
if( iMeasure / 4 != iLastMeasure / 4 ) // adjust sliding window every 4 measures
2004-07-17 05:10:11 +00:00
{
// See if there is a hold crossing the beginning of this measure
bool bHoldCrossesThisMeasure = false;
if( r )
for( int t=0; t<Original.GetNumTracks(); t++ )
2004-07-17 05:10:11 +00:00
{
if( Original.GetTapNote(t,r).type == TapNote::hold &&
Original.GetTapNote(t,r-1).type == TapNote::hold )
2004-07-17 05:10:11 +00:00
{
bHoldCrossesThisMeasure = true;
break;
}
}
// adjust offset
if( !bHoldCrossesThisMeasure )
{
iCurTrackOffset += bOffsetIncreasing ? 1 : -1;
if( iCurTrackOffset == iTrackOffsetMin || iCurTrackOffset == iTrackOffsetMax )
bOffsetIncreasing ^= true;
CLAMP( iCurTrackOffset, iTrackOffsetMin, iTrackOffsetMax );
}
}
iLastMeasure = iMeasure;
2004-07-17 05:10:11 +00:00
}
out.Convert4sToHoldNotes();
}
void NoteDataUtil::LoadOverlapped( const NoteData &input, NoteData &out, int iNewNumTracks )
{
out.SetNumTracks( iNewNumTracks );
NoteData in;
in.To4s( input );
/* 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];
2004-10-05 10:52:36 +00:00
for( int tr = 0; tr < MAX_NOTE_TRACKS; ++tr )
2004-07-17 05:10:11 +00:00
{
LastSourceTrack[tr] = -1;
LastSourceRow[tr] = -9999;
DestRow[tr] = tr;
wrap( DestRow[tr], iNewNumTracks );
}
const int ShiftThreshold = BeatToNoteRow(1);
const int iLastRow = in.GetLastRow();
for( int row = 0; row <= iLastRow; ++row )
2004-07-17 05:10:11 +00:00
{
for ( int i = 0; i < in.GetNumTracks(); i++ )
{
const int iTrackFrom = i;
int &iTrackTo = DestRow[i];
2004-10-24 10:20:24 +00:00
const TapNote &tnFrom = in.GetTapNote( iTrackFrom, row );
if( tnFrom.type == TapNote::empty )
2004-07-17 05:10:11 +00:00
continue;
if( LastSourceTrack[iTrackTo] != iTrackFrom &&
row - LastSourceRow[iTrackTo] < ShiftThreshold )
{
/* This destination track is in use by a different source track. Use the
* least-recently-used track. */
for( int DestTrack = 0; DestTrack < iNewNumTracks; ++DestTrack )
if( LastSourceRow[DestTrack] < LastSourceRow[iTrackTo] )
iTrackTo = DestTrack;
}
/* If it's still in use, then we just don't have an available track. */
if( LastSourceTrack[iTrackTo] != iTrackFrom &&
row - LastSourceRow[iTrackTo] < ShiftThreshold )
continue;
LastSourceTrack[iTrackTo] = iTrackFrom;
LastSourceRow[iTrackTo] = row;
2004-10-24 10:20:24 +00:00
out.SetTapNote( iTrackTo, row, tnFrom );
2004-07-17 05:10:11 +00:00
}
}
out.Convert4sToHoldNotes();
}
2004-05-20 19:05:37 +00:00
void NoteDataUtil::LoadTransformedLights( const NoteData &in, NoteData &out, int iNewNumTracks )
{
// reset all notes
out.Init();
NoteData Original;
Original.To4s( in );
out.Config(in);
out.SetNumTracks( iNewNumTracks );
int iLastRow = Original.GetLastRow();
for( int r=0; r <= iLastRow; ++r )
2004-05-20 19:05:37 +00:00
{
if( Original.IsRowEmpty( r ) )
continue;
/* Enable every track in the output. If there are any hold notes in the source
* on this row, output hold notes; otherwise tap notes. This is to 1: prevent
* converting everything to hold notes, and 2: so if it's written to an SM, we
* don't write huge streams of tap notes. */
bool bHoldNoteOnThisRow = false;
for( int t=0; t<Original.GetNumTracks(); t++ )
if( Original.GetTapNote(t,r).type == TapNote::hold )
2004-05-20 19:05:37 +00:00
bHoldNoteOnThisRow = true;
for( int t=0; t<out.GetNumTracks(); t++ )
{
TapNote tn = bHoldNoteOnThisRow ? TAP_ORIGINAL_HOLD : TAP_ORIGINAL_TAP;
2004-05-20 19:05:37 +00:00
out.SetTapNote( t, r, tn );
}
}
out.Convert4sToHoldNotes();
}
2004-07-11 07:21:33 +00:00
void NoteDataUtil::GetRadarValues( const NoteData &in, float fSongSeconds, RadarValues& out )
2003-08-07 06:36:34 +00:00
{
2004-07-11 07:21:33 +00:00
// The for loop and the assert are used to ensure that all fields of
// RadarValue get set in here.
FOREACH_RadarCategory( rc )
2003-08-07 06:36:34 +00:00
{
2004-07-11 07:21:33 +00:00
switch( rc )
{
case RADAR_STREAM: out[rc] = GetStreamRadarValue( in, fSongSeconds ); break;
case RADAR_VOLTAGE: out[rc] = GetVoltageRadarValue( in, fSongSeconds ); break;
case RADAR_AIR: out[rc] = GetAirRadarValue( in, fSongSeconds ); break;
case RADAR_FREEZE: out[rc] = GetFreezeRadarValue( in, fSongSeconds ); break;
case RADAR_CHAOS: out[rc] = GetChaosRadarValue( in, fSongSeconds ); break;
case RADAR_NUM_TAPS_AND_HOLDS: out[rc] = (float) in.GetNumRowsWithTapOrHoldHead(); break;
case RADAR_NUM_JUMPS: out[rc] = (float) in.GetNumDoubles(); break;
case RADAR_NUM_HOLDS: out[rc] = (float) in.GetNumHoldNotes(); break;
case RADAR_NUM_MINES: out[rc] = (float) in.GetNumMines(); break;
case RADAR_NUM_HANDS: out[rc] = (float) in.GetNumHands(); break;
default: ASSERT(0);
}
2003-08-07 06:36:34 +00:00
}
}
float NoteDataUtil::GetStreamRadarValue( const NoteData &in, float fSongSeconds )
{
if( !fSongSeconds )
return 0.0f;
// density of steps
2004-05-24 04:26:54 +00:00
int iNumNotes = in.GetNumTapNotes() + in.GetNumHoldNotes();
2003-08-07 06:36:34 +00:00
float fNotesPerSecond = iNumNotes/fSongSeconds;
float fReturn = fNotesPerSecond / 7;
return min( fReturn, 1.0f );
}
float NoteDataUtil::GetVoltageRadarValue( const NoteData &in, float fSongSeconds )
{
if( !fSongSeconds )
return 0.0f;
const float fLastBeat = in.GetLastBeat();
const float fAvgBPS = fLastBeat / fSongSeconds;
// peak density of steps
float fMaxDensitySoFar = 0;
const int BEAT_WINDOW = 8;
for( int i=0; i<=int(fLastBeat); i+=BEAT_WINDOW )
{
2004-05-24 04:26:54 +00:00
int iNumNotesThisWindow = in.GetNumTapNotes((float)i,(float)i+BEAT_WINDOW) + in.GetNumHoldNotes((float)i,(float)i+BEAT_WINDOW);
2003-08-07 06:36:34 +00:00
float fDensityThisWindow = iNumNotesThisWindow/(float)BEAT_WINDOW;
fMaxDensitySoFar = max( fMaxDensitySoFar, fDensityThisWindow );
}
float fReturn = fMaxDensitySoFar*fAvgBPS/10;
return min( fReturn, 1.0f );
}
float NoteDataUtil::GetAirRadarValue( const NoteData &in, float fSongSeconds )
{
if( !fSongSeconds )
return 0.0f;
// number of doubles
int iNumDoubles = in.GetNumDoubles();
float fReturn = iNumDoubles / fSongSeconds;
return min( fReturn, 1.0f );
}
float NoteDataUtil::GetFreezeRadarValue( const NoteData &in, float fSongSeconds )
{
if( !fSongSeconds )
return 0.0f;
// number of hold steps
float fReturn = in.GetNumHoldNotes() / fSongSeconds;
return min( fReturn, 1.0f );
}
float NoteDataUtil::GetChaosRadarValue( const NoteData &in, float fSongSeconds )
{
if( !fSongSeconds )
return 0.0f;
// count number of triplets or 16ths
int iNumChaosNotes = 0;
FOREACH_NONEMPTY_ROW_ALL_TRACKS( in, r )
2003-08-07 06:36:34 +00:00
{
if( GetNoteType(r) >= NOTE_TYPE_12TH )
2003-08-07 06:36:34 +00:00
iNumChaosNotes++;
}
float fReturn = iNumChaosNotes / fSongSeconds * 0.5f;
return min( fReturn, 1.0f );
}
2003-11-07 08:45:21 +00:00
void NoteDataUtil::RemoveHoldNotes(NoteData &in, float fStartBeat, float fEndBeat)
2003-08-07 06:36:34 +00:00
{
2003-12-16 04:00:39 +00:00
int iStartIndex = BeatToNoteRow( fStartBeat );
int iEndIndex = BeatToNoteRow( fEndBeat );
2004-05-24 04:17:19 +00:00
// turn all the HoldNotes into TapNotes
2003-11-11 08:25:32 +00:00
for( int i=in.GetNumHoldNotes()-1; i>=0; i-- ) // iterate backwards so we can delete
2003-08-07 06:36:34 +00:00
{
2003-12-19 07:50:23 +00:00
const HoldNote hn = in.GetHoldNote( i );
if( !hn.RangeOverlaps(iStartIndex,iEndIndex) )
2003-11-07 08:45:21 +00:00
continue; // skip
2003-08-07 06:36:34 +00:00
2003-11-07 08:45:21 +00:00
in.RemoveHoldNote( i );
in.SetTapNote( hn.iTrack, hn.iStartRow, TAP_ORIGINAL_TAP );
2003-11-07 08:45:21 +00:00
}
2003-08-07 06:36:34 +00:00
}
2004-01-11 06:34:30 +00:00
void NoteDataUtil::RemoveSimultaneousNotes(NoteData &in, int iMaxSimultaneous, float fStartBeat, float fEndBeat)
2004-01-02 11:25:21 +00:00
{
int iStartIndex = BeatToNoteRow( fStartBeat );
int iEndIndex = BeatToNoteRow( fEndBeat );
2004-05-24 04:17:19 +00:00
// turn all the HoldNotes into TapNotes
FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( in, r, iStartIndex, iEndIndex )
2004-01-02 11:25:21 +00:00
{
2004-05-15 03:00:43 +00:00
set<int> viTracksHeld;
2004-01-02 11:25:21 +00:00
in.GetTracksHeldAtRow( r, viTracksHeld );
// remove the first tap note or the first hold note that starts on this row
int iTotalTracksPressed = in.GetNumTracksWithTap(r) + viTracksHeld.size();
2004-01-11 06:34:30 +00:00
int iTracksToRemove = max( 0, iTotalTracksPressed - iMaxSimultaneous );
2004-01-02 11:25:21 +00:00
for( int t=0; iTracksToRemove>0 && t<in.GetNumTracks(); t++ )
{
if( in.GetTapNote(t,r).type == TapNote::tap )
2004-01-02 11:25:21 +00:00
{
in.SetTapNote( t, r, TAP_EMPTY );
iTracksToRemove--;
}
else if( in.GetTapNote(t,r).type == TapNote::hold_head )
2004-01-02 11:25:21 +00:00
{
2004-01-03 04:29:42 +00:00
int i;
2004-01-02 11:25:21 +00:00
for( i=0; i<in.GetNumHoldNotes(); i++ )
{
const HoldNote& hn = in.GetHoldNote(i);
if( hn.iStartRow == r )
break;
}
ASSERT( i<in.GetNumHoldNotes() ); // if we hit this, there was a hold head with no hold
in.RemoveHoldNote( i );
iTracksToRemove--;
}
}
}
}
2004-10-24 10:20:24 +00:00
void NoteDataUtil::RemoveJumps( NoteData &inout, float fStartBeat, float fEndBeat )
2004-01-11 06:34:30 +00:00
{
2004-10-24 10:20:24 +00:00
RemoveSimultaneousNotes(inout,1);
2004-01-11 06:34:30 +00:00
}
2004-10-24 10:20:24 +00:00
void NoteDataUtil::RemoveHands( NoteData &inout, float fStartBeat, float fEndBeat )
2004-01-11 06:34:30 +00:00
{
2004-10-24 10:20:24 +00:00
RemoveSimultaneousNotes(inout,2);
2004-01-11 06:34:30 +00:00
}
2004-01-02 11:25:21 +00:00
2004-10-24 10:20:24 +00:00
void NoteDataUtil::RemoveQuads( NoteData &inout, float fStartBeat, float fEndBeat )
2004-01-12 03:47:55 +00:00
{
2004-10-24 10:20:24 +00:00
RemoveSimultaneousNotes(inout,3);
2004-01-12 03:47:55 +00:00
}
2004-10-24 10:20:24 +00:00
void NoteDataUtil::RemoveMines(NoteData &inout, float fStartBeat, float fEndBeat )
{
2003-11-07 08:45:21 +00:00
int iRowStart = BeatToNoteRow(fStartBeat);
int iRowEnd = BeatToNoteRow(fEndBeat);
2004-10-24 10:20:24 +00:00
for( int t=0; t<inout.GetNumTracks(); t++ )
FOREACH_NONEMPTY_ROW_IN_TRACK_RANGE( inout, t, r, iRowStart, iRowEnd )
if( inout.GetTapNote(t,r).type == TapNote::mine )
inout.SetTapNote( t, r, TAP_EMPTY );
}
void NoteDataUtil::RemoveAllButOneTap( NoteData &inout, int row )
{
if(row < 0) return;
int track;
for( track = 0; track < inout.GetNumTracks(); ++track )
{
if( inout.GetTapNote(track, row).type == TapNote::tap )
break;
}
track++;
for( ; track < inout.GetNumTracks(); ++track )
{
if( inout.GetTapNote(track, row).type == TapNote::tap )
inout.SetTapNote(track, row, TAP_EMPTY );
}
}
2004-01-02 21:02:06 +00:00
static void GetTrackMapping( StepsType st, NoteDataUtil::TrackMapping tt, int NumTracks, int *iTakeFromTrack )
2003-08-07 06:36:34 +00:00
{
2004-01-02 21:02:06 +00:00
// Identity transform for cases not handled below.
2004-10-24 10:20:24 +00:00
for( int t = 0; t < MAX_NOTE_TRACKS; ++t )
2004-01-02 21:02:06 +00:00
iTakeFromTrack[t] = t;
2003-08-07 06:36:34 +00:00
switch( tt )
{
2003-12-16 04:35:43 +00:00
case NoteDataUtil::left:
case NoteDataUtil::right:
2003-08-07 06:36:34 +00:00
// Is there a way to do this withoutn handling each StepsType? -Chris
switch( st )
{
case STEPS_TYPE_DANCE_SINGLE:
case STEPS_TYPE_DANCE_DOUBLE:
case STEPS_TYPE_DANCE_COUPLE:
iTakeFromTrack[0] = 2;
iTakeFromTrack[1] = 0;
iTakeFromTrack[2] = 3;
iTakeFromTrack[3] = 1;
iTakeFromTrack[4] = 6;
iTakeFromTrack[5] = 4;
iTakeFromTrack[6] = 7;
iTakeFromTrack[7] = 5;
break;
case STEPS_TYPE_DANCE_SOLO:
iTakeFromTrack[0] = 5;
iTakeFromTrack[1] = 4;
iTakeFromTrack[2] = 0;
iTakeFromTrack[3] = 3;
iTakeFromTrack[4] = 1;
iTakeFromTrack[5] = 2;
break;
case STEPS_TYPE_PUMP_SINGLE:
case STEPS_TYPE_PUMP_COUPLE:
iTakeFromTrack[0] = 3;
iTakeFromTrack[1] = 4;
iTakeFromTrack[2] = 2;
iTakeFromTrack[3] = 0;
iTakeFromTrack[4] = 1;
iTakeFromTrack[5] = 8;
iTakeFromTrack[6] = 9;
iTakeFromTrack[7] = 7;
iTakeFromTrack[8] = 5;
iTakeFromTrack[9] = 6;
break;
case STEPS_TYPE_PUMP_HALFDOUBLE:
iTakeFromTrack[0] = 2;
iTakeFromTrack[1] = 0;
iTakeFromTrack[2] = 1;
iTakeFromTrack[3] = 3;
iTakeFromTrack[4] = 4;
iTakeFromTrack[5] = 5;
break;
case STEPS_TYPE_PUMP_DOUBLE:
iTakeFromTrack[0] = 8;
iTakeFromTrack[1] = 9;
iTakeFromTrack[2] = 7;
iTakeFromTrack[3] = 5;
iTakeFromTrack[4] = 6;
iTakeFromTrack[5] = 3;
iTakeFromTrack[6] = 4;
iTakeFromTrack[7] = 2;
iTakeFromTrack[8] = 0;
iTakeFromTrack[9] = 1;
break;
default: break;
}
2003-12-16 04:35:43 +00:00
if( tt == NoteDataUtil::right )
2003-08-07 06:36:34 +00:00
{
/* Invert. */
int iTrack[MAX_NOTE_TRACKS];
memcpy( iTrack, iTakeFromTrack, sizeof(iTrack) );
2004-10-24 10:20:24 +00:00
for( int t = 0; t < MAX_NOTE_TRACKS; ++t )
2003-08-07 06:36:34 +00:00
{
const int to = iTrack[t];
iTakeFromTrack[to] = t;
}
}
break;
2003-12-16 04:35:43 +00:00
case NoteDataUtil::mirror:
2004-10-24 10:20:24 +00:00
for( int t=0; t<NumTracks; t++ )
2003-12-16 04:35:43 +00:00
iTakeFromTrack[t] = NumTracks-t-1;
2003-08-07 06:36:34 +00:00
break;
2003-12-16 04:35:43 +00:00
case NoteDataUtil::shuffle:
case NoteDataUtil::super_shuffle: // use shuffle code to mix up HoldNotes without creating impossible patterns
2003-08-07 06:36:34 +00:00
{
// TRICKY: Shuffle so that both player get the same shuffle mapping
// in the same round.
2004-07-01 18:32:19 +00:00
int iOrig[MAX_NOTE_TRACKS];
memcpy( iOrig, iTakeFromTrack, sizeof(iOrig) );
2004-07-01 18:32:19 +00:00
int iShuffleSeed = GAMESTATE->m_iRoundSeed;
do {
RandomGen rnd( iShuffleSeed );
random_shuffle( &iTakeFromTrack[0], &iTakeFromTrack[NumTracks], rnd );
iShuffleSeed++;
2003-08-07 06:36:34 +00:00
}
2004-07-01 18:32:19 +00:00
while ( !memcmp( iOrig, iTakeFromTrack, sizeof(iOrig) ) );
2003-08-07 06:36:34 +00:00
}
break;
2004-01-02 21:02:06 +00:00
case NoteDataUtil::stomp:
switch( st )
{
case STEPS_TYPE_DANCE_SINGLE:
case STEPS_TYPE_DANCE_COUPLE:
iTakeFromTrack[0] = 3;
iTakeFromTrack[1] = 2;
iTakeFromTrack[2] = 1;
iTakeFromTrack[3] = 0;
iTakeFromTrack[4] = 7;
iTakeFromTrack[5] = 6;
iTakeFromTrack[6] = 5;
iTakeFromTrack[7] = 4;
break;
2004-05-08 00:08:29 +00:00
case STEPS_TYPE_DANCE_DOUBLE:
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;
2004-01-02 21:02:06 +00:00
default:
break;
}
break;
2003-08-07 06:36:34 +00:00
default:
ASSERT(0);
}
}
2004-10-24 10:20:24 +00:00
static void SuperShuffleTaps( NoteData &inout, int iStartIndex, int iEndIndex )
2003-08-07 06:36:34 +00:00
{
2005-01-10 06:23:04 +00:00
// convertTo4s before calling this
ASSERT( inout.GetNumHoldNotes() == 0 );
2003-12-16 04:35:43 +00:00
/* We already did the normal shuffling code above, which did a good job
* of shuffling HoldNotes without creating impossible patterns.
2004-05-24 04:17:19 +00:00
* Now, go in and shuffle the TapNotes per-row.
2003-12-16 04:35:43 +00:00
*
* This is only called by NoteDataUtil::Turn. "in" is in 4s, and iStartIndex
* and iEndIndex are in range. */
2004-10-24 10:20:24 +00:00
FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( inout, r, iStartIndex, iEndIndex )
2003-08-07 06:36:34 +00:00
{
2004-10-24 10:20:24 +00:00
for( int t1=0; t1<inout.GetNumTracks(); t1++ )
2003-08-07 06:36:34 +00:00
{
2005-01-10 06:23:04 +00:00
const TapNote tn1 = inout.GetTapNote(t1, r);
if( tn1.type == TapNote::hold )
2003-12-16 04:35:43 +00:00
continue;
// a tap that is not part of a hold
// probe for a spot to swap with
2005-01-10 06:23:04 +00:00
for( int i=0; i<4; i++ )
2003-08-07 06:36:34 +00:00
{
2004-10-24 10:20:24 +00:00
const int t2 = rand() % inout.GetNumTracks();
2005-01-10 06:23:04 +00:00
const TapNote tn2 = inout.GetTapNote(t2, r);
if( tn2.type == TapNote::hold ) // a tap that is not part of a hold
2003-12-16 04:35:43 +00:00
continue;
// swap
2004-10-24 10:20:24 +00:00
inout.SetTapNote(t1, r, tn2);
inout.SetTapNote(t2, r, tn1);
2003-12-16 04:35:43 +00:00
break;
2003-08-07 06:36:34 +00:00
}
}
}
2003-12-16 04:35:43 +00:00
}
2004-10-24 10:20:24 +00:00
void NoteDataUtil::Turn( NoteData &inout, StepsType st, TrackMapping tt, float fStartBeat, float fEndBeat )
2003-12-16 04:35:43 +00:00
{
int iTakeFromTrack[MAX_NOTE_TRACKS]; // New track "t" will take from old track iTakeFromTrack[t]
2004-10-24 10:20:24 +00:00
GetTrackMapping( st, tt, inout.GetNumTracks(), iTakeFromTrack );
2003-12-16 04:35:43 +00:00
if( fEndBeat == -1 )
2004-10-24 10:20:24 +00:00
fEndBeat = inout.GetLastBeat();
2003-12-16 04:35:43 +00:00
int iStartIndex = BeatToNoteRow( fStartBeat );
int iEndIndex = BeatToNoteRow( fEndBeat );
/* Clamp to known-good ranges. */
iStartIndex = max( iStartIndex, 0 );
2004-10-24 10:20:24 +00:00
iEndIndex = min( iEndIndex, inout.GetLastRow() );
2003-12-16 04:35:43 +00:00
NoteData tempNoteData;
2005-01-10 06:23:04 +00:00
tempNoteData.LoadTransformed( inout, inout.GetNumTracks(), iTakeFromTrack );
2003-12-16 04:35:43 +00:00
if( tt == super_shuffle )
2005-01-10 06:23:04 +00:00
{
NoteData tempNoteData2; // write into here as we tranform
tempNoteData2.To4s( tempNoteData );
SuperShuffleTaps( tempNoteData2, iStartIndex, iEndIndex ); /* expects 4s */
tempNoteData.From4s( tempNoteData2 );
}
2003-12-16 04:35:43 +00:00
2005-01-10 06:23:04 +00:00
inout.CopyAll( tempNoteData );
2003-08-07 06:36:34 +00:00
}
2004-10-24 10:20:24 +00:00
void NoteDataUtil::Backwards( NoteData &inout )
2003-08-07 06:36:34 +00:00
{
2004-10-24 10:20:24 +00:00
inout.ConvertHoldNotesTo4s();
int max_row = inout.GetLastRow();
for( int t=0; t<inout.GetNumTracks(); t++ )
2003-08-07 06:36:34 +00:00
{
2005-01-09 21:34:19 +00:00
/* XXX: This is wrong. Is it worth fixing? (When is "Backwards" useful?) */
2004-10-24 10:20:24 +00:00
FOREACH_NONEMPTY_ROW_IN_TRACK_RANGE( inout, t, r, 0, max_row/2 )
2003-08-07 06:36:34 +00:00
{
int iRowEarlier = r;
int iRowLater = max_row-r;
2004-10-24 10:20:24 +00:00
const TapNote &tnEarlier = inout.GetTapNote(t, iRowEarlier);
const TapNote &tnLater = inout.GetTapNote(t, iRowLater);
inout.SetTapNote(t, iRowEarlier, tnLater);
inout.SetTapNote(t, iRowLater, tnEarlier);
2003-08-07 06:36:34 +00:00
}
}
2004-10-24 10:20:24 +00:00
inout.Convert4sToHoldNotes();
2003-08-07 06:36:34 +00:00
}
2004-10-24 10:20:24 +00:00
void NoteDataUtil::SwapSides( NoteData &inout )
2003-08-07 06:36:34 +00:00
{
2004-10-24 10:20:24 +00:00
inout.ConvertHoldNotesTo4s();
/* XXX: This is broken. */
2004-10-24 10:20:24 +00:00
for( int t=0; t<inout.GetNumTracks()/2; t++ )
2003-08-07 06:36:34 +00:00
{
2004-10-24 10:20:24 +00:00
FOREACH_NONEMPTY_ROW_IN_TRACK( inout, t, r )
2003-08-07 06:36:34 +00:00
{
int iTrackEarlier = t;
2004-10-24 10:20:24 +00:00
int iTrackLater = t + inout.GetNumTracks()/2 + inout.GetNumTracks()%2;
2003-08-07 06:36:34 +00:00
// swap
2004-10-24 10:20:24 +00:00
const TapNote &tnEarlier = inout.GetTapNote(iTrackEarlier, r);
const TapNote &tnLater = inout.GetTapNote(iTrackLater, r);
inout.SetTapNote(iTrackEarlier, r, tnLater);
inout.SetTapNote(iTrackLater, r, tnEarlier);
2003-08-07 06:36:34 +00:00
}
}
2004-10-24 10:20:24 +00:00
inout.Convert4sToHoldNotes();
2003-08-07 06:36:34 +00:00
}
2004-10-24 10:20:24 +00:00
void NoteDataUtil::Little(NoteData &inout, float fStartBeat, float fEndBeat)
2003-08-07 06:36:34 +00:00
{
if( fEndBeat == -1 )
2004-10-24 10:20:24 +00:00
fEndBeat = inout.GetLastBeat();
int iStartIndex = BeatToNoteRow( fStartBeat );
int iEndIndex = BeatToNoteRow( fEndBeat );
2003-08-07 06:36:34 +00:00
// filter out all non-quarter notes
2004-10-24 10:20:24 +00:00
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 )
2004-10-24 10:20:24 +00:00
inout.SetTapNote( t, i, TAP_EMPTY );
2003-08-07 06:36:34 +00:00
2004-10-24 10:20:24 +00:00
for( int i=inout.GetNumHoldNotes()-1; i>=0; i-- )
if( fmodf(inout.GetHoldNote(i).GetStartBeat(),1) != 0 ) // doesn't start on a beat
inout.RemoveHoldNote( i );
2003-08-07 06:36:34 +00:00
}
2004-10-24 10:20:24 +00:00
void NoteDataUtil::Wide( NoteData &inout, float fStartBeat, float fEndBeat )
2003-08-07 06:36:34 +00:00
{
2003-08-18 02:37:43 +00:00
// Make all all quarter notes into jumps.
2004-10-24 10:20:24 +00:00
inout.ConvertHoldNotesTo4s();
2003-08-07 06:36:34 +00:00
/* Start on an even beat. */
2004-11-01 11:02:54 +00:00
fStartBeat = Quantize( fStartBeat, 2.0f );
2003-10-27 04:23:08 +00:00
const int first_row = BeatToNoteRow( fStartBeat );
2004-10-24 10:20:24 +00:00
const int last_row = min( BeatToNoteRow(fEndBeat), inout.GetLastRow() );
2003-08-18 02:37:43 +00:00
for( int i=first_row; i<last_row; i+=ROWS_PER_BEAT*2 ) // every even beat
2003-08-07 06:36:34 +00:00
{
2004-10-24 10:20:24 +00:00
if( inout.GetNumTapNonEmptyTracks(i) != 1 )
2003-08-07 06:36:34 +00:00
continue; // skip. Don't place during holds
2004-10-24 10:20:24 +00:00
if( inout.GetNumTracksWithTap(i) != 1 )
2003-08-07 06:36:34 +00:00
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 )
2004-10-24 10:20:24 +00:00
if( j!=i && inout.GetNumTapNonEmptyTracks(j) > 0 )
2003-08-07 06:36:34 +00:00
{
bSpaceAroundIsEmpty = false;
break;
}
if( !bSpaceAroundIsEmpty )
continue; // skip
// add a note determinitsitcally
int iBeat = (int)roundf( NoteRowToBeat(i) );
2004-10-24 10:20:24 +00:00
int iTrackOfNote = inout.GetFirstTrackWithTap(i);
2003-08-07 06:36:34 +00:00
int iTrackToAdd = iTrackOfNote + (iBeat%5)-2; // won't be more than 2 tracks away from the existing note
2004-10-24 10:20:24 +00:00
CLAMP( iTrackToAdd, 0, inout.GetNumTracks()-1 );
2003-08-07 06:36:34 +00:00
if( iTrackToAdd == iTrackOfNote )
iTrackToAdd++;
2004-10-24 10:20:24 +00:00
CLAMP( iTrackToAdd, 0, inout.GetNumTracks()-1 );
2003-08-07 06:36:34 +00:00
if( iTrackToAdd == iTrackOfNote )
iTrackToAdd--;
2004-10-24 10:20:24 +00:00
CLAMP( iTrackToAdd, 0, inout.GetNumTracks()-1 );
2003-08-07 06:36:34 +00:00
2004-10-24 10:20:24 +00:00
if( inout.GetTapNote(iTrackToAdd, i).type != TapNote::empty )
2003-08-07 06:36:34 +00:00
{
2004-10-24 10:20:24 +00:00
iTrackToAdd = (iTrackToAdd+1) % inout.GetNumTracks();
2003-08-07 06:36:34 +00:00
}
2004-10-24 10:20:24 +00:00
inout.SetTapNote(iTrackToAdd, i, TAP_ADDITION_TAP);
2003-08-07 06:36:34 +00:00
}
2004-10-24 10:20:24 +00:00
inout.Convert4sToHoldNotes();
2003-08-07 06:36:34 +00:00
}
2004-10-24 10:20:24 +00:00
void NoteDataUtil::Big( NoteData &inout, float fStartBeat, float fEndBeat )
2003-08-07 06:36:34 +00:00
{
2004-10-24 10:20:24 +00:00
InsertIntelligentTaps(inout,1.0f,0.5f,1.0f,false,fStartBeat,fEndBeat); // add 8ths between 4ths
2003-08-07 06:36:34 +00:00
}
2004-10-24 10:20:24 +00:00
void NoteDataUtil::Quick( NoteData &inout, float fStartBeat, float fEndBeat )
2003-08-07 06:36:34 +00:00
{
2004-10-24 10:20:24 +00:00
InsertIntelligentTaps(inout,0.5f,0.25f,1.0f,false,fStartBeat,fEndBeat); // add 16ths between 8ths
2003-08-07 06:36:34 +00:00
}
// 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.
2004-10-24 10:20:24 +00:00
void NoteDataUtil::BMRize( NoteData &inout, float fStartBeat, float fEndBeat )
{
2004-10-24 10:20:24 +00:00
Big( inout, fStartBeat, fEndBeat );
Quick( inout, fStartBeat, fEndBeat );
}
2004-10-24 10:20:24 +00:00
void NoteDataUtil::Skippy( NoteData &inout, float fStartBeat, float fEndBeat )
2003-08-07 06:36:34 +00:00
{
2004-10-24 10:20:24 +00:00
InsertIntelligentTaps(inout,1.0f,0.75f,1.0f,true,fStartBeat,fEndBeat); // add 16ths between 4ths
2003-08-07 06:36:34 +00:00
}
2004-03-01 08:59:22 +00:00
void NoteDataUtil::InsertIntelligentTaps(
2004-10-24 10:20:24 +00:00
NoteData &inout,
2004-03-01 08:59:22 +00:00
float fWindowSizeBeats,
float fInsertOffsetBeats,
float fWindowStrideBeats,
bool bSkippy,
float fStartBeat,
float fEndBeat )
2003-08-07 06:36:34 +00:00
{
2003-11-07 06:15:33 +00:00
ASSERT( fInsertOffsetBeats <= fWindowSizeBeats );
ASSERT( fWindowSizeBeats <= fWindowStrideBeats );
2004-10-24 10:20:24 +00:00
inout.ConvertHoldNotesTo4s();
2003-08-07 06:36:34 +00:00
2004-03-01 08:59:22 +00:00
bool bRequireNoteAtBeginningOfWindow = !bSkippy;
bool bRequireNoteAtEndOfWindow = true;
2003-10-27 04:23:08 +00:00
/* Start on a multiple of fBeatInterval. */
2004-10-24 17:44:51 +00:00
fStartBeat = Quantize( fStartBeat, fWindowStrideBeats );
2003-08-18 02:37:43 +00:00
// Insert a beat in the middle of every fBeatInterval.
2003-10-27 04:23:08 +00:00
const int first_row = BeatToNoteRow( fStartBeat );
2004-10-24 10:20:24 +00:00
const int last_row = min( BeatToNoteRow(fEndBeat), inout.GetLastRow() );
2003-08-18 02:37:43 +00:00
2003-11-07 06:15:33 +00:00
const int rows_per_window = BeatToNoteRow( fWindowSizeBeats );
const int rows_per_stride = BeatToNoteRow( fWindowStrideBeats );
const int insert_row_offset = BeatToNoteRow( fInsertOffsetBeats );
2003-08-18 02:37:43 +00:00
2003-11-07 06:15:33 +00:00
for( int i=first_row; i<last_row; i+=rows_per_stride )
2003-08-07 06:36:34 +00:00
{
int iRowEarlier = i;
2003-11-07 06:15:33 +00:00
int iRowLater = i + rows_per_window;
2003-08-07 06:36:34 +00:00
int iRowToAdd = i + insert_row_offset;
// 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.
2004-03-01 08:59:22 +00:00
if( bRequireNoteAtBeginningOfWindow )
2004-10-24 10:20:24 +00:00
if( inout.GetNumTapNonEmptyTracks(iRowEarlier)!=1 || inout.GetNumTracksWithTapOrHoldHead(iRowEarlier)!=1 )
2004-03-01 08:59:22 +00:00
continue;
if( bRequireNoteAtEndOfWindow )
2004-10-24 10:20:24 +00:00
if( inout.GetNumTapNonEmptyTracks(iRowLater)!=1 || inout.GetNumTracksWithTapOrHoldHead(iRowLater)!=1 )
2004-03-01 08:59:22 +00:00
continue;
2003-08-07 06:36:34 +00:00
// 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;
FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( inout, j, iRowEarlier+1, iRowLater-1 )
bNoteInMiddle = true;
2003-08-07 06:36:34 +00:00
if( bNoteInMiddle )
continue;
// add a note deterministically somewhere on a track different from the two surrounding notes
2004-10-24 10:20:24 +00:00
int iTrackOfNoteEarlier = -1;
bool bEarlierHasNonEmptyTrack = inout.GetTapFirstNonEmptyTrack( iRowEarlier, iTrackOfNoteEarlier );
int iTrackOfNoteLater = -1;
2005-01-22 17:21:40 +00:00
inout.GetTapFirstNonEmptyTrack( iRowLater, iTrackOfNoteLater );
2003-08-07 06:36:34 +00:00
int iTrackOfNoteToAdd = 0;
2003-08-18 02:37:43 +00:00
if( bSkippy &&
2003-08-07 06:36:34 +00:00
iTrackOfNoteEarlier != iTrackOfNoteLater ) // Don't make skips on the same note
{
2004-10-24 10:20:24 +00:00
if( bEarlierHasNonEmptyTrack )
2004-03-01 08:59:22 +00:00
{
iTrackOfNoteToAdd = iTrackOfNoteEarlier;
goto done_looking_for_track_to_add;
}
2003-08-07 06:36:34 +00:00
}
2004-01-02 22:19:27 +00:00
// try to choose a track between the earlier and later notes
if( abs(iTrackOfNoteEarlier-iTrackOfNoteLater) >= 2 )
2003-08-07 06:36:34 +00:00
{
2004-01-02 22:19:27 +00:00
iTrackOfNoteToAdd = min(iTrackOfNoteEarlier,iTrackOfNoteLater)+1;
goto done_looking_for_track_to_add;
2003-08-07 06:36:34 +00:00
}
2004-01-02 22:19:27 +00:00
2004-03-01 08:59:22 +00:00
// try to choose a track just to the left
2004-01-02 22:19:27 +00:00
if( min(iTrackOfNoteEarlier,iTrackOfNoteLater)-1 >= 0 )
{
iTrackOfNoteToAdd = min(iTrackOfNoteEarlier,iTrackOfNoteLater)-1;
goto done_looking_for_track_to_add;
}
2004-03-01 08:59:22 +00:00
// try to choose a track just to the right
2004-10-24 10:20:24 +00:00
if( max(iTrackOfNoteEarlier,iTrackOfNoteLater)+1 < inout.GetNumTracks() )
2004-01-02 22:19:27 +00:00
{
iTrackOfNoteToAdd = max(iTrackOfNoteEarlier,iTrackOfNoteLater)+1;
goto done_looking_for_track_to_add;
}
done_looking_for_track_to_add:
2004-10-24 10:20:24 +00:00
inout.SetTapNote(iTrackOfNoteToAdd, iRowToAdd, TAP_ADDITION_TAP);
2003-08-07 06:36:34 +00:00
}
2004-10-24 10:20:24 +00:00
inout.Convert4sToHoldNotes();
2003-08-07 06:36:34 +00:00
}
2004-10-24 10:20:24 +00:00
void NoteDataUtil::AddMines( NoteData &inout, float fStartBeat, float fEndBeat )
2003-08-18 02:37:43 +00:00
{
2003-10-27 04:23:08 +00:00
const int first_row = BeatToNoteRow( fStartBeat );
2004-10-24 10:20:24 +00:00
const int last_row = min( BeatToNoteRow(fEndBeat), inout.GetLastRow() );
2003-08-18 02:37:43 +00:00
2003-08-23 19:01:35 +00:00
//
// Change whole rows at a time to be tap notes. Otherwise, it causes
// major problems for our scoring system. -Chris
//
int iRowCount = 0;
2004-01-02 04:08:25 +00:00
int iPlaceEveryRows = 6;
FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( inout, r, first_row, last_row )
2003-08-31 22:38:06 +00:00
{
iRowCount++;
2003-08-31 22:38:06 +00:00
// 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++ )
if( inout.GetTapNote(t,r).type == TapNote::tap )
inout.SetTapNote(t,r,TAP_ADDITION_MINE);
iRowCount = 0;
if( iPlaceEveryRows == 6 )
iPlaceEveryRows = 7;
else
iPlaceEveryRows = 6;
2003-08-23 19:01:35 +00:00
}
2003-08-31 22:38:06 +00:00
}
2003-11-12 08:13:02 +00:00
// Place mines right after hold so player must lift their foot.
2004-10-24 10:20:24 +00:00
for( int i=0; i<inout.GetNumHoldNotes(); i++ )
2003-08-31 22:38:06 +00:00
{
2004-10-24 10:20:24 +00:00
HoldNote &hn = inout.GetHoldNote(i);
2003-12-16 04:00:39 +00:00
float fHoldEndBeat = hn.GetEndBeat();
float fMineBeat = fHoldEndBeat+0.5f;
int iMineRow = BeatToNoteRow( fMineBeat );
2003-08-31 23:15:24 +00:00
if( iMineRow < first_row || iMineRow > last_row )
continue;
// Only place a mines if there's not another step nearby
int iMineRangeBegin = BeatToNoteRow( fMineBeat-0.5f ) + 1;
int iMineRangeEnd = BeatToNoteRow( fMineBeat+0.5f ) - 1;
2004-10-24 10:20:24 +00:00
if( !inout.IsRangeEmpty(hn.iTrack, iMineRangeBegin, iMineRangeEnd) )
continue;
// Add a mine right after the hold end.
2004-10-24 10:20:24 +00:00
inout.SetTapNote( hn.iTrack, iMineRow, TAP_ADDITION_MINE );
2003-08-31 22:38:06 +00:00
2004-10-24 10:20:24 +00:00
// Convert all notes inout this row to mines.
for( int t=0; t<inout.GetNumTracks(); t++ )
if( inout.GetTapNote(t,iMineRow).type == TapNote::tap )
inout.SetTapNote(t,iMineRow,TAP_ADDITION_MINE);
iRowCount = 0;
2003-08-31 22:38:06 +00:00
}
2003-08-18 02:37:43 +00:00
}
2004-10-24 10:20:24 +00:00
void NoteDataUtil::Echo( NoteData &inout, float fStartBeat, float fEndBeat )
2003-11-07 05:17:41 +00:00
{
// add 8th note tap "echos" after all taps
int iEchoTrack = -1;
2004-10-24 17:44:51 +00:00
fStartBeat = Quantize( fStartBeat, 0.5 );
2003-11-07 05:17:41 +00:00
const int first_row = BeatToNoteRow( fStartBeat );
2004-10-24 10:20:24 +00:00
const int last_row = min( BeatToNoteRow(fEndBeat), inout.GetLastRow() );
2003-11-07 05:17:41 +00:00
const int rows_per_interval = BeatToNoteRow( 0.5 );
// window is one beat wide and slides 1/2 a beat at a time
for( int r=first_row; r<last_row; r+=rows_per_interval )
{
2003-11-08 01:57:04 +00:00
const int iRowWindowBegin = r;
const int iRowWindowEnd = r + rows_per_interval*2;
2003-11-07 05:17:41 +00:00
2004-10-24 10:20:24 +00:00
const int iFirstTapInRow = inout.GetFirstTrackWithTap(iRowWindowBegin);
2003-11-07 05:17:41 +00:00
if( iFirstTapInRow != -1 )
iEchoTrack = iFirstTapInRow;
2003-11-08 01:57:04 +00:00
2003-11-07 09:27:28 +00:00
if( iEchoTrack==-1 )
continue; // don't lay
2003-11-07 05:17:41 +00:00
// 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;
2003-11-07 05:17:41 +00:00
if( bTapInMiddle )
2003-11-07 09:27:28 +00:00
continue; // don't lay
2003-11-07 05:17:41 +00:00
2003-11-08 01:57:04 +00:00
const int iRowEcho = r + rows_per_interval;
2003-11-07 09:27:28 +00:00
{
2004-05-15 03:00:43 +00:00
set<int> viTracks;
2004-10-24 10:20:24 +00:00
inout.GetTracksHeldAtRow( iRowEcho, viTracks );
2003-11-07 09:27:28 +00:00
// 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
}
2003-11-07 05:17:41 +00:00
2004-10-24 10:20:24 +00:00
inout.SetTapNote( iEchoTrack, iRowEcho, TAP_ADDITION_TAP );
2003-11-07 05:17:41 +00:00
}
}
2004-10-24 10:20:24 +00:00
void NoteDataUtil::Planted( NoteData &inout, float fStartBeat, float fEndBeat )
2003-11-07 08:45:21 +00:00
{
2004-10-24 10:20:24 +00:00
ConvertTapsToHolds( inout, 1, fStartBeat, fEndBeat );
2003-11-07 08:45:21 +00:00
}
2004-10-24 10:20:24 +00:00
void NoteDataUtil::Floored( NoteData &inout, float fStartBeat, float fEndBeat )
2003-11-07 08:45:21 +00:00
{
2004-10-24 10:20:24 +00:00
ConvertTapsToHolds( inout, 2, fStartBeat, fEndBeat );
2003-11-07 08:45:21 +00:00
}
2004-10-24 10:20:24 +00:00
void NoteDataUtil::Twister( NoteData &inout, float fStartBeat, float fEndBeat )
2004-08-29 21:26:52 +00:00
{
2004-10-24 10:20:24 +00:00
ConvertTapsToHolds( inout, 3, fStartBeat, fEndBeat );
2004-08-29 21:26:52 +00:00
}
2004-10-24 10:20:24 +00:00
void NoteDataUtil::ConvertTapsToHolds( NoteData &inout, int iSimultaneousHolds, float fStartBeat, float fEndBeat )
2003-11-07 05:17:41 +00:00
{
// Convert all taps to freezes.
const int first_row = BeatToNoteRow( fStartBeat );
2004-10-24 10:20:24 +00:00
const int last_row = min( BeatToNoteRow(fEndBeat), inout.GetLastRow() );
FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( inout, r, first_row, last_row )
2003-11-07 05:17:41 +00:00
{
2004-05-15 03:00:43 +00:00
int iTrackAddedThisRow = 0;
2004-10-24 10:20:24 +00:00
for( int t=0; t<inout.GetNumTracks(); t++ )
2003-11-07 05:17:41 +00:00
{
2004-05-15 03:00:43 +00:00
if( iTrackAddedThisRow > iSimultaneousHolds )
break;
2004-10-24 10:20:24 +00:00
if( inout.GetTapNote(t,r).type == TapNote::tap )
2003-11-07 05:17:41 +00:00
{
2004-05-15 03:00:43 +00:00
// Find the ending row for this hold
2003-11-07 08:45:21 +00:00
int iTapsLeft = iSimultaneousHolds;
2004-05-15 03:00:43 +00:00
int r2 = r+1;
FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( inout, next_row, r+1, last_row )
2003-11-07 05:17:41 +00:00
{
r2 = next_row;
2004-05-15 03:00:43 +00:00
// If there are two taps in a row on the same track,
2004-05-15 03:00:43 +00:00
// don't convert the earlier one to a hold.
2004-10-24 10:20:24 +00:00
if( inout.GetTapNote(t,r2).type != TapNote::empty )
2004-05-15 03:00:43 +00:00
goto dont_add_hold;
set<int> tracksDown;
2004-10-24 10:20:24 +00:00
inout.GetTracksHeldAtRow( r2, tracksDown );
inout.GetTapNonEmptyTracks( r2, tracksDown );
2004-05-15 03:00:43 +00:00
iTapsLeft -= tracksDown.size();
if( iTapsLeft == 0 )
break; // we found the ending row for this hold
else if( iTapsLeft < 0 )
goto dont_add_hold;
2003-11-07 05:17:41 +00:00
}
float fStartBeat = NoteRowToBeat(r);
float fEndBeat = NoteRowToBeat(r2);
2004-05-15 03:00:43 +00:00
// If the steps end in a tap, convert that tap
2003-11-07 05:17:41 +00:00
// to a hold that lasts for at least one beat.
if( r2==r+1 )
fEndBeat = fStartBeat+1;
2004-05-15 03:00:43 +00:00
2004-10-24 10:20:24 +00:00
inout.AddHoldNote( HoldNote(t,BeatToNoteRow(fStartBeat),BeatToNoteRow(fEndBeat)) );
2004-05-15 03:00:43 +00:00
iTrackAddedThisRow++;
2003-11-07 05:17:41 +00:00
}
dont_add_hold:
2003-11-08 01:57:04 +00:00
;
2003-11-07 05:17:41 +00:00
}
}
2004-05-15 03:00:43 +00:00
2003-11-07 05:17:41 +00:00
}
2004-10-24 10:20:24 +00:00
void NoteDataUtil::Stomp( NoteData &inout, StepsType st, float fStartBeat, float fEndBeat )
2003-11-07 05:17:41 +00:00
{
// Make all non jumps with ample space around them into jumps.
const int first_row = BeatToNoteRow( fStartBeat );
2004-10-24 10:20:24 +00:00
const int last_row = min( BeatToNoteRow(fEndBeat), inout.GetLastRow() );
2003-11-07 05:17:41 +00:00
2004-01-02 21:02:06 +00:00
int iTrackMapping[MAX_NOTE_TRACKS];
2004-10-24 10:20:24 +00:00
GetTrackMapping( st, stomp, inout.GetNumTracks(), iTrackMapping );
2004-01-02 21:02:06 +00:00
FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( inout, r, first_row, last_row )
2003-11-07 05:17:41 +00:00
{
2004-10-24 10:20:24 +00:00
if( inout.GetNumTracksWithTap(r) != 1 )
2003-11-07 05:17:41 +00:00
continue; // skip
2004-10-24 10:20:24 +00:00
for( int t=0; t<inout.GetNumTracks(); t++ )
2003-11-07 05:17:41 +00:00
{
2004-10-24 10:20:24 +00:00
if( inout.GetTapNote(t, r).type == TapNote::tap ) // there is a tap here
2003-11-07 05:17:41 +00: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.5);
int iRowWindowEnd = r + BeatToNoteRow(0.5);
bool bTapInMiddle = false;
for( int r2=iRowWindowBegin+1; r2<=iRowWindowEnd-1; r2++ )
2004-10-24 10:20:24 +00:00
if( inout.IsThereATapAtRow(r2) && r2 != r ) // don't count the note we're looking around
2003-11-07 05:17:41 +00:00
{
bTapInMiddle = true;
break;
}
if( bTapInMiddle )
continue;
// don't convert to jump if there's a hold here
2004-10-24 10:20:24 +00:00
int iNumTracksHeld = inout.GetNumTracksHeldAtRow(r);
2003-11-07 05:17:41 +00:00
if( iNumTracksHeld >= 1 )
continue;
2004-01-02 21:02:06 +00:00
int iOppositeTrack = iTrackMapping[t];
2004-10-24 10:20:24 +00:00
inout.SetTapNote( iOppositeTrack, r, TAP_ADDITION_TAP );
2003-11-07 05:17:41 +00:00
}
}
}
}
2003-08-07 06:36:34 +00:00
2004-10-24 10:20:24 +00:00
void NoteDataUtil::SnapToNearestNoteType( NoteData &inout, NoteType nt1, NoteType nt2, float fBeginBeat, float fEndBeat )
2003-08-07 06:36:34 +00:00
{
// nt2 is optional and should be NOTE_TYPE_INVALID if it is not used
2003-08-07 06:36:34 +00:00
float fSnapInterval1, fSnapInterval2;
switch( nt1 )
{
case NOTE_TYPE_4TH: fSnapInterval1 = 1/1.0f; break;
case NOTE_TYPE_8TH: fSnapInterval1 = 1/2.0f; break;
case NOTE_TYPE_12TH: fSnapInterval1 = 1/3.0f; break;
case NOTE_TYPE_16TH: fSnapInterval1 = 1/4.0f; break;
case NOTE_TYPE_24TH: fSnapInterval1 = 1/6.0f; break;
case NOTE_TYPE_32ND: fSnapInterval1 = 1/8.0f; break;
2003-11-03 08:44:49 +00:00
case NOTE_TYPE_48TH: fSnapInterval1 = 1/12.0f; break;
case NOTE_TYPE_64TH: fSnapInterval1 = 1/16.0f; break;
2003-08-07 06:36:34 +00:00
default: ASSERT( false ); return;
}
switch( nt2 )
{
case NOTE_TYPE_4TH: fSnapInterval2 = 1/1.0f; break;
case NOTE_TYPE_8TH: fSnapInterval2 = 1/2.0f; break;
case NOTE_TYPE_12TH: fSnapInterval2 = 1/3.0f; break;
case NOTE_TYPE_16TH: fSnapInterval2 = 1/4.0f; break;
case NOTE_TYPE_24TH: fSnapInterval2 = 1/6.0f; break;
case NOTE_TYPE_32ND: fSnapInterval2 = 1/8.0f; break;
2003-11-03 08:44:49 +00:00
case NOTE_TYPE_48TH: fSnapInterval2 = 1/12.0f; break;
case NOTE_TYPE_64TH: fSnapInterval2 = 1/16.0f; break;
case NOTE_TYPE_INVALID: fSnapInterval2 = 10000; break; // nothing will ever snap to this. That's what we want!
2003-08-07 06:36:34 +00:00
default: ASSERT( false ); return;
}
2004-10-24 00:17:15 +00:00
int rowBegin = BeatToNoteRow( fBeginBeat );
int rowEnd = BeatToNoteRow( fEndBeat );
2003-08-07 06:36:34 +00:00
2004-10-24 10:20:24 +00:00
inout.ConvertHoldNotesTo2sAnd3s();
2003-08-07 06:36:34 +00:00
2004-05-24 04:17:19 +00:00
// iterate over all TapNotes in the interval and snap them
FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( inout, i, rowBegin, rowEnd )
2003-08-07 06:36:34 +00:00
{
int iOldIndex = i;
float fOldBeat = NoteRowToBeat( iOldIndex );
2004-10-24 17:44:51 +00:00
float fNewBeat1 = Quantize( fOldBeat, fSnapInterval1 );
float fNewBeat2 = Quantize( fOldBeat, fSnapInterval2 );
2003-08-07 06:36:34 +00:00
bool bNewBeat1IsCloser = fabsf(fNewBeat1-fOldBeat) < fabsf(fNewBeat2-fOldBeat);
float fNewBeat = bNewBeat1IsCloser ? fNewBeat1 : fNewBeat2;
int iNewIndex = BeatToNoteRow( fNewBeat );
2004-10-24 10:20:24 +00:00
for( int c=0; c<inout.GetNumTracks(); c++ )
2003-08-07 06:36:34 +00:00
{
if( iOldIndex == iNewIndex )
continue;
2004-10-24 10:20:24 +00:00
TapNote tnNew = inout.GetTapNote(c, iOldIndex);
if( tnNew.type == TapNote::empty )
2003-08-07 06:36:34 +00:00
continue;
2004-10-24 10:20:24 +00:00
inout.SetTapNote(c, iOldIndex, TAP_EMPTY);
2003-08-07 06:36:34 +00:00
2004-10-24 10:20:24 +00:00
const TapNote &tnOld = inout.GetTapNote(c, iNewIndex);
if( tnNew.type == TapNote::tap &&
(tnOld.type == TapNote::hold_head || tnOld.type == TapNote::hold_tail) )
2004-05-24 04:17:19 +00:00
continue; // HoldNotes override TapNotes
2003-08-07 06:36:34 +00:00
2004-10-24 10:20:24 +00:00
/* If two hold tnNew boundaries are getting snapped together,
2003-08-07 06:36:34 +00:00
* merge them. */
2004-10-24 10:20:24 +00:00
if( (tnNew.type == TapNote::hold_head && tnOld.type == TapNote::hold_tail) ||
(tnNew.type == TapNote::hold_tail && tnOld.type == TapNote::hold_head) )
tnNew = TAP_EMPTY;
2003-08-07 06:36:34 +00:00
2004-10-24 10:20:24 +00:00
inout.SetTapNote(c, iNewIndex, tnNew );
2003-08-07 06:36:34 +00:00
}
}
2004-10-24 10:20:24 +00:00
inout.Convert2sAnd3sToHoldNotes();
2003-08-07 06:36:34 +00:00
}
2004-10-24 10:20:24 +00:00
void NoteDataUtil::CopyLeftToRight( NoteData &inout )
2003-08-07 06:36:34 +00:00
{
2004-10-24 10:20:24 +00:00
inout.ConvertHoldNotesTo4s();
for( int t=0; t<inout.GetNumTracks()/2; t++ )
2003-08-07 06:36:34 +00:00
{
FOREACH_NONEMPTY_ROW_IN_TRACK( inout, t, r )
2003-08-07 06:36:34 +00:00
{
int iTrackEarlier = t;
2004-10-24 10:20:24 +00:00
int iTrackLater = inout.GetNumTracks()-1-t;
2003-08-07 06:36:34 +00:00
2004-10-24 10:20:24 +00:00
const TapNote &tnEarlier = inout.GetTapNote(iTrackEarlier, r);
inout.SetTapNote(iTrackLater, r, tnEarlier);
2003-08-07 06:36:34 +00:00
}
}
2004-10-24 10:20:24 +00:00
inout.Convert4sToHoldNotes();
2003-08-07 06:36:34 +00:00
}
2004-10-24 10:20:24 +00:00
void NoteDataUtil::CopyRightToLeft( NoteData &inout )
2003-08-07 06:36:34 +00:00
{
2004-10-24 10:20:24 +00:00
inout.ConvertHoldNotesTo4s();
for( int t=0; t<inout.GetNumTracks()/2; t++ )
2003-08-07 06:36:34 +00:00
{
FOREACH_NONEMPTY_ROW_IN_TRACK( inout, t, r )
2003-08-07 06:36:34 +00:00
{
int iTrackEarlier = t;
2004-10-24 10:20:24 +00:00
int iTrackLater = inout.GetNumTracks()-1-t;
2003-08-07 06:36:34 +00:00
2004-10-24 10:20:24 +00:00
TapNote tnLater = inout.GetTapNote(iTrackLater, r);
inout.SetTapNote(iTrackEarlier, r, tnLater);
2003-08-07 06:36:34 +00:00
}
}
2004-10-24 10:20:24 +00:00
inout.Convert4sToHoldNotes();
2003-08-07 06:36:34 +00:00
}
2004-10-24 10:20:24 +00:00
void NoteDataUtil::ClearLeft( NoteData &inout )
2003-08-07 06:36:34 +00:00
{
2004-10-24 10:20:24 +00:00
inout.ConvertHoldNotesTo4s();
for( int t=0; t<inout.GetNumTracks()/2; t++ )
FOREACH_NONEMPTY_ROW_IN_TRACK( inout, t, r )
2004-10-24 10:20:24 +00:00
inout.SetTapNote(t, r, TAP_EMPTY);
inout.Convert4sToHoldNotes();
2003-08-07 06:36:34 +00:00
}
2004-10-24 10:20:24 +00:00
void NoteDataUtil::ClearRight( NoteData &inout )
2003-08-07 06:36:34 +00:00
{
2004-10-24 10:20:24 +00:00
inout.ConvertHoldNotesTo4s();
for( int t=(inout.GetNumTracks()+1)/2; t<inout.GetNumTracks(); t++ )
FOREACH_NONEMPTY_ROW_IN_TRACK( inout, t, r )
2004-10-24 10:20:24 +00:00
inout.SetTapNote(t, r, TAP_EMPTY);
inout.Convert4sToHoldNotes();
2003-08-07 06:36:34 +00:00
}
2004-10-24 10:20:24 +00:00
void NoteDataUtil::CollapseToOne( NoteData &inout )
2003-08-07 06:36:34 +00:00
{
2004-10-24 10:20:24 +00:00
inout.ConvertHoldNotesTo2sAnd3s();
FOREACH_NONEMPTY_ROW_ALL_TRACKS( inout, r )
2004-10-24 10:20:24 +00:00
for( int t=0; t<inout.GetNumTracks(); t++ )
if( inout.GetTapNote(t,r).type != TapNote::empty )
2003-08-07 06:36:34 +00:00
{
2004-10-24 10:20:24 +00:00
TapNote tn = inout.GetTapNote(t,r);
inout.SetTapNote(t, r, TAP_EMPTY);
inout.SetTapNote(0, r, tn);
2003-08-07 06:36:34 +00:00
}
2004-10-24 10:20:24 +00:00
inout.Convert2sAnd3sToHoldNotes();
2003-08-07 06:36:34 +00:00
}
2004-10-24 10:20:24 +00:00
void NoteDataUtil::CollapseLeft( NoteData &inout )
2003-12-31 07:49:30 +00:00
{
2004-10-24 10:20:24 +00:00
inout.ConvertHoldNotesTo2sAnd3s();
FOREACH_NONEMPTY_ROW_ALL_TRACKS( inout, r )
2003-12-31 07:49:30 +00:00
{
int iNumTracksFilled = 0;
2004-10-24 10:20:24 +00:00
for( int t=0; t<inout.GetNumTracks(); t++ )
{
2004-10-24 10:20:24 +00:00
if( inout.GetTapNote(t,r).type != TapNote::empty )
2003-12-31 07:49:30 +00:00
{
2004-10-24 10:20:24 +00:00
TapNote tn = inout.GetTapNote(t,r);
inout.SetTapNote(t, r, TAP_EMPTY);
if( iNumTracksFilled < inout.GetNumTracks() )
{
inout.SetTapNote(iNumTracksFilled, r, tn);
++iNumTracksFilled;
}
2003-12-31 07:49:30 +00:00
}
}
2003-12-31 07:49:30 +00:00
}
2004-10-24 10:20:24 +00:00
inout.Convert2sAnd3sToHoldNotes();
2003-12-31 07:49:30 +00:00
}
2004-10-24 10:20:24 +00:00
void NoteDataUtil::ShiftLeft( NoteData &inout )
2003-08-07 06:36:34 +00:00
{
2004-10-24 10:20:24 +00:00
inout.ConvertHoldNotesTo4s();
FOREACH_NONEMPTY_ROW_ALL_TRACKS( inout, r )
2003-08-07 06:36:34 +00:00
{
2004-10-24 10:20:24 +00:00
for( int t=0; t<inout.GetNumTracks()-1; t++ ) // inout.GetNumTracks()-1 times
2003-08-07 06:36:34 +00:00
{
int iTrackEarlier = t;
2004-10-24 10:20:24 +00:00
int iTrackLater = (t+1) % inout.GetNumTracks();
2003-08-07 06:36:34 +00:00
// swap
2004-10-24 10:20:24 +00:00
TapNote tnEarlier = inout.GetTapNote(iTrackEarlier, r);
TapNote tnLater = inout.GetTapNote(iTrackLater, r);
inout.SetTapNote(iTrackEarlier, r, tnLater);
inout.SetTapNote(iTrackLater, r, tnEarlier);
2003-08-07 06:36:34 +00:00
}
}
2004-10-24 10:20:24 +00:00
inout.Convert4sToHoldNotes();
2003-08-07 06:36:34 +00:00
}
2004-10-24 10:20:24 +00:00
void NoteDataUtil::ShiftRight( NoteData &inout )
2003-08-07 06:36:34 +00:00
{
2004-10-24 10:20:24 +00:00
inout.ConvertHoldNotesTo4s();
FOREACH_NONEMPTY_ROW_ALL_TRACKS( inout, r )
2003-08-07 06:36:34 +00:00
{
2004-10-24 10:20:24 +00:00
for( int t=inout.GetNumTracks()-1; t>0; t-- ) // inout.GetNumTracks()-1 times
2003-08-07 06:36:34 +00:00
{
int iTrackEarlier = t;
2004-10-24 10:20:24 +00:00
int iTrackLater = (t+1) % inout.GetNumTracks();
2003-08-07 06:36:34 +00:00
// swap
2004-10-24 10:20:24 +00:00
TapNote tnEarlier = inout.GetTapNote(iTrackEarlier, r);
TapNote tnLater = inout.GetTapNote(iTrackLater, r);
inout.SetTapNote(iTrackEarlier, r, tnLater);
inout.SetTapNote(iTrackLater, r, tnEarlier);
2003-08-07 06:36:34 +00:00
}
}
2004-10-24 10:20:24 +00:00
inout.Convert4sToHoldNotes();
2003-08-07 06:36:34 +00:00
}
struct ValidRow
{
StepsType st;
bool bValidMask[MAX_NOTE_TRACKS];
};
#define T true
#define f false
const ValidRow g_ValidRows[] =
{
{ STEPS_TYPE_DANCE_DOUBLE, { T,T,T,T,f,f,f,f } },
{ STEPS_TYPE_DANCE_DOUBLE, { f,T,T,T,T,f,f,f } },
{ STEPS_TYPE_DANCE_DOUBLE, { f,f,f,T,T,T,T,f } },
{ STEPS_TYPE_DANCE_DOUBLE, { f,f,f,f,T,T,T,T } },
};
2003-08-07 06:36:34 +00:00
2004-10-24 10:20:24 +00:00
void NoteDataUtil::FixImpossibleRows( NoteData &inout, StepsType st )
2003-08-07 06:36:34 +00:00
{
2003-09-21 23:16:44 +00:00
vector<const ValidRow*> vpValidRowsToCheck;
2003-09-24 09:39:09 +00:00
for( unsigned i=0; i<ARRAYSIZE(g_ValidRows); i++ )
{
2003-09-21 23:16:44 +00:00
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( inout, r )
2003-09-21 23:16:44 +00:00
{
// only check rows with jumps
2004-10-24 10:20:24 +00:00
if( inout.GetNumTapNonEmptyTracks(r) < 2 )
2003-09-21 23:16:44 +00:00
continue;
bool bPassedOneMask = false;
for( unsigned i=0; i<vpValidRowsToCheck.size(); i++ )
{
const ValidRow &vr = *vpValidRowsToCheck[i];
2004-10-24 10:20:24 +00:00
if( NoteDataUtil::RowPassesValidMask(inout,r,vr.bValidMask) )
2003-09-21 23:16:44 +00:00
{
bPassedOneMask = true;
break;
}
}
2003-09-21 23:16:44 +00:00
if( !bPassedOneMask )
RemoveAllButOneTap( inout, r );
}
}
2004-10-24 10:20:24 +00:00
bool NoteDataUtil::RowPassesValidMask( NoteData &inout, int row, const bool bValidMask[] )
{
2004-10-24 10:20:24 +00:00
for( int t=0; t<inout.GetNumTracks(); t++ )
2003-09-21 23:16:44 +00:00
{
2004-10-24 10:20:24 +00:00
if( !bValidMask[t] && inout.GetTapNote(t,row).type != TapNote::empty )
return false;
2003-09-21 23:16:44 +00:00
}
return true;
}
2004-10-24 10:20:24 +00:00
void NoteDataUtil::ConvertAdditionsToRegular( NoteData &inout )
{
for( int t=0; t<inout.GetNumTracks(); t++ )
FOREACH_NONEMPTY_ROW_IN_TRACK( inout, t, r )
2004-10-24 10:20:24 +00:00
if( inout.GetTapNote(t,r).source == TapNote::addition )
{
2004-10-24 10:20:24 +00:00
TapNote tn = inout.GetTapNote(t,r);
tn.source = TapNote::original;
2004-10-24 10:20:24 +00:00
inout.SetTapNote( t, r, tn );
}
}
2003-08-10 10:12:50 +00:00
void NoteDataUtil::TransformNoteData( NoteData &nd, const AttackArray &aa, StepsType st, Song* pSong )
{
FOREACH_CONST( Attack, aa, a )
{
PlayerOptions po;
po.FromString( a->sModifier );
if( po.ContainsTransformOrTurn() )
{
float fStartBeat, fEndBeat;
a->GetAttackBeats( pSong, NULL, fStartBeat, fEndBeat );
NoteDataUtil::TransformNoteData( nd, po, st, fStartBeat, fEndBeat );
}
}
}
void NoteDataUtil::TransformNoteData( NoteData &nd, const PlayerOptions &po, StepsType st, float fStartBeat, float fEndBeat )
2003-08-13 19:17:28 +00:00
{
2004-08-29 21:14:26 +00:00
// Apply remove transforms before others so that we don't go removing
// notes we just inserted.
2004-02-29 23:32:04 +00:00
if( po.m_bTransforms[PlayerOptions::TRANSFORM_LITTLE] ) NoteDataUtil::Little(nd, fStartBeat, fEndBeat);
2003-11-07 08:45:21 +00:00
if( po.m_bTransforms[PlayerOptions::TRANSFORM_NOHOLDS] ) NoteDataUtil::RemoveHoldNotes(nd, fStartBeat, fEndBeat);
if( po.m_bTransforms[PlayerOptions::TRANSFORM_NOMINES] ) NoteDataUtil::RemoveMines(nd, fStartBeat, fEndBeat);
2004-08-29 21:14:26 +00:00
if( po.m_bTransforms[PlayerOptions::TRANSFORM_NOJUMPS] ) NoteDataUtil::RemoveJumps(nd, fStartBeat, fEndBeat);
if( po.m_bTransforms[PlayerOptions::TRANSFORM_NOHANDS] ) NoteDataUtil::RemoveHands(nd, fStartBeat, fEndBeat);
if( po.m_bTransforms[PlayerOptions::TRANSFORM_NOQUADS] ) NoteDataUtil::RemoveQuads(nd, fStartBeat, fEndBeat);
2004-03-01 08:59:22 +00:00
2004-08-29 21:14:26 +00:00
// Apply inserts.
2003-11-07 08:45:21 +00:00
if( po.m_bTransforms[PlayerOptions::TRANSFORM_BIG] ) NoteDataUtil::Big(nd, fStartBeat, fEndBeat);
if( po.m_bTransforms[PlayerOptions::TRANSFORM_QUICK] ) NoteDataUtil::Quick(nd, fStartBeat, fEndBeat);
if( po.m_bTransforms[PlayerOptions::TRANSFORM_BMRIZE] ) NoteDataUtil::BMRize(nd, fStartBeat, fEndBeat);
2004-03-01 08:59:22 +00:00
// Skippy will still add taps to places that the other
// AddIntelligentTaps above won't.
2003-11-07 08:45:21 +00:00
if( po.m_bTransforms[PlayerOptions::TRANSFORM_SKIPPY] ) NoteDataUtil::Skippy(nd, fStartBeat, fEndBeat);
2004-03-01 08:59:22 +00:00
2004-08-29 21:14:26 +00:00
// These aren't affects by the above inserts very much.
2003-11-07 08:45:21 +00:00
if( po.m_bTransforms[PlayerOptions::TRANSFORM_MINES] ) NoteDataUtil::AddMines(nd, fStartBeat, fEndBeat);
if( po.m_bTransforms[PlayerOptions::TRANSFORM_ECHO] ) NoteDataUtil::Echo(nd, fStartBeat, fEndBeat);
2004-03-01 08:59:22 +00:00
2004-08-29 21:14:26 +00:00
// Jump-adding transforms aren't much affected by additional taps.
2004-03-01 08:59:22 +00:00
if( po.m_bTransforms[PlayerOptions::TRANSFORM_WIDE] ) NoteDataUtil::Wide(nd, fStartBeat, fEndBeat);
2004-01-02 21:02:06 +00:00
if( po.m_bTransforms[PlayerOptions::TRANSFORM_STOMP] ) NoteDataUtil::Stomp(nd, st, fStartBeat, fEndBeat);
2004-03-01 08:59:22 +00:00
// Transforms that add holds go last. If they went first, most tap-adding
2004-08-29 21:14:26 +00:00
// transforms wouldn't do anything because tap-adding transforms skip areas
// where there's a hold.
2004-03-01 08:59:22 +00:00
if( po.m_bTransforms[PlayerOptions::TRANSFORM_PLANTED] ) NoteDataUtil::Planted(nd, fStartBeat, fEndBeat);
2004-08-29 21:26:52 +00:00
if( po.m_bTransforms[PlayerOptions::TRANSFORM_FLOORED] ) NoteDataUtil::Floored(nd, fStartBeat, fEndBeat);
2003-11-07 08:45:21 +00:00
if( po.m_bTransforms[PlayerOptions::TRANSFORM_TWISTER] ) NoteDataUtil::Twister(nd, fStartBeat, fEndBeat);
2004-08-29 21:14:26 +00:00
// Apply turns and shuffles last to that they affect inserts.
if( po.m_bTurns[PlayerOptions::TURN_MIRROR] ) NoteDataUtil::Turn( nd, st, NoteDataUtil::mirror, fStartBeat, fEndBeat );
if( po.m_bTurns[PlayerOptions::TURN_LEFT] ) NoteDataUtil::Turn( nd, st, NoteDataUtil::left, fStartBeat, fEndBeat );
if( po.m_bTurns[PlayerOptions::TURN_RIGHT] ) NoteDataUtil::Turn( nd, st, NoteDataUtil::right, fStartBeat, fEndBeat );
if( po.m_bTurns[PlayerOptions::TURN_SHUFFLE] ) NoteDataUtil::Turn( nd, st, NoteDataUtil::shuffle, fStartBeat, fEndBeat );
if( po.m_bTurns[PlayerOptions::TURN_SUPER_SHUFFLE] ) NoteDataUtil::Turn( nd, st, NoteDataUtil::super_shuffle, fStartBeat, fEndBeat );
2003-08-13 19:17:28 +00:00
}
2003-11-20 06:50:05 +00: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 )
{
float fBeat = pSong->GetBeatFromElapsedTime( sec );
int iBeat = (int)fBeat;
int iTrack = iBeat % nd.GetNumTracks(); // deterministically calculates track
2004-10-23 23:41:49 +00:00
nd.SetTapAttackNote( iTrack, BeatToNoteRow(fBeat), szAttacks[rand()%ARRAYSIZE(szAttacks)], 15 );
2003-11-20 06:50:05 +00:00
}
}
#if 0 // undo this if ScaleRegion breaks more things than it fixes
2003-09-07 22:28:46 +00:00
void NoteDataUtil::Scale( NoteData &nd, float fScale )
{
ASSERT( fScale > 0 );
NoteData temp;
temp.CopyAll( &nd );
nd.ClearAll();
for( int r=0; r<=temp.GetLastRow(); r++ )
{
for( int t=0; t<temp.GetNumTracks(); t++ )
{
TapNote tn = temp.GetTapNote( t, r );
if( tn != TAP_EMPTY )
{
temp.SetTapNote( t, r, TAP_EMPTY );
2003-09-07 22:48:25 +00:00
int new_row = int(r*fScale);
2003-09-07 22:28:46 +00:00
nd.SetTapNote( t, new_row, tn );
}
}
}
}
#endif
// added to fix things in the editor - make sure that you're working off data that is
// either in 4s or in 2s and 3s.
2004-02-26 03:07:49 +00:00
void NoteDataUtil::ScaleRegion( NoteData &nd, float fScale, float fStartBeat, float fEndBeat)
{
ASSERT( fScale > 0 );
ASSERT( fStartBeat < fEndBeat );
ASSERT( fStartBeat >= 0 );
NoteData temp1, temp2;
temp1.Config( nd );
temp2.Config( nd );
2004-02-26 03:07:49 +00:00
const int iFirstRowAtEndOfRegion = min( nd.GetLastRow(), BeatToNoteRowNotRounded(fEndBeat) );
const int iScaledFirstRowAfterRegion = (int)((fStartBeat + (fEndBeat - fStartBeat) * fScale) * ROWS_PER_BEAT);
2004-02-27 00:55:25 +00:00
if( fStartBeat != 0 )
2004-10-23 17:43:49 +00:00
temp1.CopyRange( nd, 0, BeatToNoteRowNotRounded(fStartBeat) );
2004-02-27 00:55:25 +00:00
if( nd.GetLastRow() > iFirstRowAtEndOfRegion )
2004-10-23 17:43:49 +00:00
temp1.CopyRange( nd, iFirstRowAtEndOfRegion, nd.GetLastRow(), iScaledFirstRowAfterRegion);
temp2.CopyRange( nd, BeatToNoteRowNotRounded(fStartBeat), iFirstRowAtEndOfRegion );
nd.ClearAll();
for( int r=0; r<=temp2.GetLastRow(); r++ )
{
for( int t=0; t<temp2.GetNumTracks(); t++ )
{
TapNote tn = temp2.GetTapNote( t, r );
if( tn.type != TapNote::empty )
{
temp2.SetTapNote( t, r, TAP_EMPTY );
int new_row = int(r*fScale + fStartBeat*ROWS_PER_BEAT);
temp1.SetTapNote( t, new_row, tn );
}
}
}
2004-10-23 17:43:49 +00:00
nd.CopyAll( temp1 );
}
2003-09-07 22:28:46 +00:00
void NoteDataUtil::ShiftRows( NoteData &nd, float fStartBeat, float fBeatsToShift )
{
NoteData temp;
temp.SetNumTracks( nd.GetNumTracks() );
int iTakeFromRow=0;
int iPasteAtRow;
iTakeFromRow = BeatToNoteRow( fStartBeat );
iPasteAtRow = BeatToNoteRow( fStartBeat );
if( fBeatsToShift > 0 ) // add blank rows
iPasteAtRow += BeatToNoteRow( fBeatsToShift );
else // delete rows
iTakeFromRow += BeatToNoteRow( -fBeatsToShift );
2004-10-23 17:43:49 +00:00
temp.CopyRange( nd, iTakeFromRow, nd.GetLastRow() );
2003-09-07 22:28:46 +00:00
nd.ClearRange( min(iTakeFromRow,iPasteAtRow), nd.GetLastRow() );
2004-10-23 17:43:49 +00:00
nd.CopyRange( temp, 0, temp.GetLastRow(), iPasteAtRow );
2003-09-13 19:46:37 +00:00
}
2004-05-31 22:42:12 +00:00
2004-10-24 10:45:30 +00:00
void NoteDataUtil::RemoveAllTapsOfType( NoteData& ndInOut, TapNote::Type typeToRemove )
{
for( int t=0; t<ndInOut.GetNumTracks(); t++ )
{
FOREACH_NONEMPTY_ROW_IN_TRACK( ndInOut, t, row )
{
if( ndInOut.GetTapNote(t, row).type == typeToRemove )
ndInOut.SetTapNote( t, row, TAP_EMPTY );
}
}
}
void NoteDataUtil::RemoveAllTapsExceptForType( NoteData& ndInOut, TapNote::Type typeToKeep )
{
for( int t=0; t<ndInOut.GetNumTracks(); t++ )
{
FOREACH_NONEMPTY_ROW_IN_TRACK( ndInOut, t, row )
{
if( ndInOut.GetTapNote(t, row).type != typeToKeep )
ndInOut.SetTapNote( t, row, TAP_EMPTY );
}
}
}
2004-10-24 10:20:24 +00:00
2004-05-31 22:42:12 +00: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.
*/