#include "stdafx.h" /* ----------------------------------------------------------------------------- Class: NoteData Desc: See header. Copyright (c) 2001-2002 by the person(s) listed below. All rights reserved. Chris Danford ----------------------------------------------------------------------------- */ #include "NoteData.h" #include "RageUtil.h" #include "GameConstantsAndTypes.h" #include "PrefsManager.h" #include "ArrowEffects.h" #include "PrefsManager.h" #include "GameConstantsAndTypes.h" #include "RageException.h" #include "GameState.h" #include "math.h" NoteData::NoteData() { Init(); } NoteData::NoteData(const NoteData &cpy) { *this = cpy; } NoteData &NoteData::operator= (const NoteData &cpy) { Init(); for(int t = 0; t < MAX_NOTE_TRACKS; ++t) m_TapNotes[t] = cpy.m_TapNotes[t]; memmove(m_HoldNotes, cpy.m_HoldNotes, sizeof(m_HoldNotes)); m_iNumTracks = cpy.m_iNumTracks; m_iNumHoldNotes = cpy.m_iNumHoldNotes; return *this; } void NoteData::Init() { m_iNumTracks = 0; m_iNumHoldNotes = 0; } NoteData::~NoteData() { } void NoteData::LoadFromSMNoteDataString( CString sSMNoteData ) { int iNumTracks = m_iNumTracks; Init(); m_iNumTracks = iNumTracks; // strip comments out of sSMNoteData while( sSMNoteData.Find("//") != -1 ) { int iIndexCommentStart = sSMNoteData.Find("//"); int iIndexCommentEnd = sSMNoteData.Find("\n", iIndexCommentStart); if( iIndexCommentEnd == -1 ) // comment doesn't have an end? sSMNoteData.Delete( iIndexCommentStart, 2 ); else sSMNoteData.Delete( iIndexCommentStart, iIndexCommentEnd-iIndexCommentStart ); } CStringArray asMeasures; split( sSMNoteData, ",", asMeasures, true ); // ignore empty is important for( unsigned m=0; mConvert2sAnd3sToHoldNotes(); } CString NoteData::GetSMNoteDataString() { this->ConvertHoldNotesTo2sAnd3s(); float fLastBeat = GetLastBeat(); int iLastMeasure = int( fLastBeat/BEATS_PER_MEASURE ); CStringArray asMeasureStrings; for( int m=0; m<=iLastMeasure; m++ ) // foreach measure { NoteType nt = GetSmallestNoteTypeForMeasure( m ); int iRowSpacing; if( nt == NOTE_TYPE_INVALID ) iRowSpacing = 1; else iRowSpacing = int(roundf( NoteTypeToBeat(nt) * ROWS_PER_BEAT )); CStringArray asMeasureLines; asMeasureLines.push_back( ssprintf(" // measure %d", m+1) ); const int iMeasureStartRow = m * ROWS_PER_MEASURE; const int iMeasureLastRow = (m+1) * ROWS_PER_MEASURE - 1; for( int r=iMeasureStartRow; r<=iMeasureLastRow; r+=iRowSpacing ) { char szLineString[MAX_NOTE_TRACKS]; /* XXXXX: don't do this, translate explicitly, so the TAP_* constants * can be changed */ for( int t=0; tConvert2sAnd3sToHoldNotes(); return join( "\n,", asMeasureStrings ); } /* Clear [iNoteIndexBegin,iNoteIndexEnd]; that is, including iNoteIndexEnd. */ void NoteData::ClearRange( int iNoteIndexBegin, int iNoteIndexEnd ) { this->ConvertHoldNotesTo4s(); /* XXX: if iNoteIndexEnd == m_TapNotes[0].size(), just erase() */ for( int c=0; cConvert4sToHoldNotes(); } /* Copy a range from pFrom to this. (Note that this does *not* overlay; * all data in the range is overwritten.) */ void NoteData::CopyRange( NoteData* pFrom, int iFromIndexBegin, int iFromIndexEnd, int iToIndexBegin ) { ASSERT( pFrom->m_iNumTracks == m_iNumTracks ); if( iToIndexBegin == -1 ) iToIndexBegin = 0; pFrom->ConvertHoldNotesTo4s(); this->ConvertHoldNotesTo4s(); // copy recorded TapNotes int f = iFromIndexBegin, t = iToIndexBegin; while( f<=iFromIndexEnd && f < MAX_TAP_NOTE_ROWS && t < MAX_TAP_NOTE_ROWS ) { for( int c=0; cGetTapNote(c, f)); f++; t++; } pFrom->Convert4sToHoldNotes(); this->Convert4sToHoldNotes(); } void NoteData::AddHoldNote( HoldNote add ) { ASSERT( add.m_fStartBeat>=0 && add.m_fEndBeat>=0 ); int i; // look for other hold notes that overlap and merge them // XXX: this is done implicitly with 4s, but 4s uses this function. // Rework this later. for( i=0; i= add.m_fEndBeat ) || // other consumes add ( other.m_fStartBeat >= add.m_fStartBeat && other.m_fEndBeat <= add.m_fEndBeat ) || // other inside add ( add.m_fStartBeat <= other.m_fStartBeat && other.m_fStartBeat <= add.m_fEndBeat ) || // other overlaps add ( add.m_fStartBeat <= other.m_fEndBeat && other.m_fEndBeat <= add.m_fEndBeat ) // other overlaps add ) ) { add.m_fStartBeat = min(add.m_fStartBeat, other.m_fStartBeat); add.m_fEndBeat = max(add.m_fEndBeat, other.m_fEndBeat); // delete this HoldNote for( int j=i; j= 0 && iHoldIndex < m_iNumHoldNotes ); HoldNote& hn = m_HoldNotes[iHoldIndex]; int iHoldStartIndex = BeatToNoteRow(hn.m_fStartBeat); // delete a tap note at the start of this hold SetTapNote(hn.m_iTrack, iHoldStartIndex, TAP_EMPTY); // remove from list for( int j=iHoldIndex; j=0; i-- ) // iterate back to front { if( !IsRowEmpty(i) ) { fOldestBeatFoundSoFar = NoteRowToBeat(i); break; } } for( i=0; i fOldestBeatFoundSoFar ) fOldestBeatFoundSoFar = m_HoldNotes[i].m_fEndBeat; } return fOldestBeatFoundSoFar; } int NoteData::GetNumTapNotes( const float fStartBeat, const float fEndBeat ) { int iNumNotes = 0; int iStartIndex = BeatToNoteRow( fStartBeat ); int iEndIndex = BeatToNoteRow( fEndBeat ); for( int i=iStartIndex; i= 2 ) iNumDoubles++; } return iNumDoubles; } int NoteData::GetNumHoldNotes( const float fStartBeat, const float fEndBeat ) { int iNumHolds = 0; for( int i=0; i=0; i-- ) // foreach HoldNote { if( c >= iFirstRightSideColumn ) { // delete this HoldNote by shifting everything down for( int j=i; j=0; i-- ) // foreach HoldNote { HoldNote &hn = m_HoldNotes[i]; if( hn.m_iTrack < iFirstRightSideColumn ) { // delete this HoldNote by shifting everything down RemoveHoldNote( i ); } else { m_HoldNotes[i].m_iTrack -= 4; } } } void NoteData::RemoveHoldNotes() { // turn all the HoldNotes into TapNotes for( int i=0; iGetCurrentStyleDef()->m_NotesType ) { case NOTES_TYPE_DANCE_SINGLE: case NOTES_TYPE_DANCE_DOUBLE: case NOTES_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 NOTES_TYPE_DANCE_SOLO: iTakeFromTrack[0] = 5; iTakeFromTrack[1] = 4; iTakeFromTrack[2] = 0; iTakeFromTrack[3] = 3; iTakeFromTrack[4] = 1; iTakeFromTrack[5] = 2; break; case NOTES_TYPE_PUMP_SINGLE: case NOTES_TYPE_PUMP_DOUBLE: case NOTES_TYPE_PUMP_COUPLE: iTakeFromTrack[0] = 1; iTakeFromTrack[1] = 3; iTakeFromTrack[2] = 2; iTakeFromTrack[3] = 4; iTakeFromTrack[4] = 0; iTakeFromTrack[5] = 6; iTakeFromTrack[6] = 8; iTakeFromTrack[7] = 7; iTakeFromTrack[8] = 9; iTakeFromTrack[9] = 5; break; case NOTES_TYPE_EZ2_SINGLE: case NOTES_TYPE_EZ2_DOUBLE: case NOTES_TYPE_EZ2_REAL: // identity transform. What should we do here? iTakeFromTrack[0] = 0; iTakeFromTrack[1] = 1; iTakeFromTrack[2] = 2; iTakeFromTrack[3] = 3; iTakeFromTrack[4] = 4; iTakeFromTrack[5] = 5; iTakeFromTrack[6] = 6; iTakeFromTrack[7] = 7; iTakeFromTrack[8] = 8; iTakeFromTrack[9] = 9; iTakeFromTrack[10]= 10; iTakeFromTrack[11]= 11; break; } break; case PlayerOptions::TURN_SHUFFLE: case PlayerOptions::TURN_SUPER_SHUFFLE: // use this code to shuffle the HoldNotes { CArray aiTracksLeftToMap; for( t=0; tConvertHoldNotesTo2sAnd3s(); // transform notes for( t=0; tCopyAll( &tempNoteData ); // copy note data from newData back into this this->Convert2sAnd3sToHoldNotes(); if( tt == PlayerOptions::TURN_SUPER_SHUFFLE ) { // 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 some more. // clear tempNoteData because we're going to use it as a scratch buffer again tempNoteData.Init(); tempNoteData.m_iNumTracks = this->m_iNumTracks; // copy all HoldNotes before copying taps for( int i=0; im_iNumHoldNotes; i++ ) tempNoteData.AddHoldNote( this->m_HoldNotes[i] ); this->ConvertHoldNotesTo4s(); for( int r=0; r<=this->GetLastRow(); r++ ) // foreach row { if( this->IsRowEmpty(r) ) continue; // no need to super shuffle this row // shuffle this row CArray aiTracksThatCouldHaveTapNotes; for( t=0; tCopyAll( &tempNoteData ); // copy note data from newData back into this } } void NoteData::MakeLittle() { // filter out all non-quarter notes for( int i=0; i= NOTE_TYPE_12TH ) iNumChaosNotes++; } float fReturn = iNumChaosNotes / fSongSeconds * 0.5f; return min( fReturn, 1.0f ); } float NoteData::GetFreezeRadarValue( float fSongSeconds ) { // number of hold steps float fReturn = GetNumHoldNotes() / fSongSeconds; return min( fReturn, 1.0f ); } // -1 for iOriginalTracksToTakeFrom means no track void NoteData::LoadTransformed( NoteData* pOriginal, int iNewNumTracks, const int iOriginalTrackToTakeFrom[] ) { // reset all notes Init(); pOriginal->ConvertHoldNotesTo4s(); m_iNumTracks = iNewNumTracks; // copy tracks for( int t=0; tm_iNumTracks ); if( iOriginalTrack == -1 ) continue; m_TapNotes[t] = pOriginal->m_TapNotes[iOriginalTrack]; } pOriginal->Convert4sToHoldNotes(); Convert4sToHoldNotes(); } void NoteData::LoadTransformedSlidingWindow( NoteData* pOriginal, int iNewNumTracks ) { // reset all notes Init(); pOriginal->ConvertHoldNotesTo4s(); m_iNumTracks = iNewNumTracks; int iCurTrackOffset = 0; int iTrackOffsetMin = 0; int iTrackOffsetMax = abs( iNewNumTracks - pOriginal->m_iNumTracks ); int bOffsetIncreasing = true; int iLastRow = pOriginal->GetLastRow(); for( int r=0; r<=iLastRow; r++ ) { // copy notes in this measure for( int t=0; tm_iNumTracks; t++ ) { int iOldTrack = t; int iNewTrack = (iOldTrack + iCurTrackOffset) % iNewNumTracks; if( pOriginal->GetTapNote(iOldTrack, r) != TAP_EMPTY ) this->SetTapNote(iNewTrack, r, pOriginal->GetTapNote(iOldTrack, r)); } if( r % (ROWS_PER_MEASURE*4) == 0 ) // adjust sliding window every 4 measures { // See if there is a hold crossing the beginning of this measure bool bHoldCrossesThisMeasure = false; #if 0 pOriginal->Convert4sToHoldNotes(); for( int i=0; im_iNumHoldNotes; i++ ) { const HoldNote& hn = pOriginal->m_HoldNotes[i]; /* Bug here: NoteRowToBeat() may have floating point error, * so we need to do an epsilon here. But we can do this in * 4s easier anyway ... */ if( hn.m_fStartBeat < NoteRowToBeat(r) && hn.m_fEndBeat > NoteRowToBeat(r) ) { bHoldCrossesThisMeasure = true; break; } } pOriginal->ConvertHoldNotesTo4s(); #endif if( r ) for( int t=0; t<=pOriginal->m_iNumTracks; t++ ) { if( pOriginal->GetTapNote(t, r) == TAP_HOLD && pOriginal->GetTapNote(t, r-1) == TAP_HOLD) { bHoldCrossesThisMeasure = true; break; } } // adjust offset if( !bHoldCrossesThisMeasure ) { iCurTrackOffset += bOffsetIncreasing ? 1 : -1; if( iCurTrackOffset == iTrackOffsetMin || iCurTrackOffset == iTrackOffsetMax ) bOffsetIncreasing ^= true; CLAMP( iCurTrackOffset, iTrackOffsetMin, iTrackOffsetMax ); } } } pOriginal->Convert4sToHoldNotes(); Convert4sToHoldNotes(); } NoteType NoteData::GetSmallestNoteTypeForMeasure( 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 pad(needed+1, TAP_EMPTY); for(int track = 0; track < MAX_NOTE_TRACKS; ++track) m_TapNotes[track].insert(m_TapNotes[track].end(), pad.begin(), pad.end()); } void NoteData::MoveTapNoteTrack(int dest, int src) { if(dest == src) return; m_TapNotes[dest] = m_TapNotes[src]; m_TapNotes[src].clear(); } void NoteData::SetTapNote(int track, int row, TapNote t) { if(row < 0) return; PadTapNotes(row); m_TapNotes[track][row]=t; }