#include "stdafx.h" /* ----------------------------------------------------------------------------- Class: Notes Desc: Hold Notes data and metadata. Does not hold gameplay-time information, like keeping track of which Notes have been stepped on (PlayerSteps does that). Copyright (c) 2001-2002 by the person(s) listed below. All rights reserved. ----------------------------------------------------------------------------- */ #include "Notes.h" #include "Song.h" #include "Notes.h" #include "IniFile.h" #include "math.h" // for fabs() #include "RageUtil.h" #include "RageLog.h" #include "GameInput.h" typedef int DanceNote; enum { DANCE_NOTE_NONE = 0, DANCE_NOTE_PAD1_LEFT, DANCE_NOTE_PAD1_UPLEFT, DANCE_NOTE_PAD1_DOWN, DANCE_NOTE_PAD1_UP, DANCE_NOTE_PAD1_UPRIGHT, DANCE_NOTE_PAD1_RIGHT, DANCE_NOTE_PAD2_LEFT, DANCE_NOTE_PAD2_UPLEFT, DANCE_NOTE_PAD2_DOWN, DANCE_NOTE_PAD2_UP, DANCE_NOTE_PAD2_UPRIGHT, DANCE_NOTE_PAD2_RIGHT }; Notes::Notes() { m_NotesType = NOTES_TYPE_DANCE_SINGLE; m_DifficultyClass = CLASS_EASY; m_iMeter = 0; for( int r=0; r 40 ) { cNoteCharOut = '2'; iBMSTrack -= 40; } else { cNoteCharOut = '1'; } switch( iBMSTrack ) { case 11: iDanceColOut = DANCE_NOTE_PAD1_LEFT; break; case 12: iDanceColOut = DANCE_NOTE_PAD1_UPLEFT; break; case 13: iDanceColOut = DANCE_NOTE_PAD1_DOWN; break; case 14: iDanceColOut = DANCE_NOTE_PAD1_UP; break; case 15: iDanceColOut = DANCE_NOTE_PAD1_UPRIGHT; break; case 16: iDanceColOut = DANCE_NOTE_PAD1_RIGHT; break; case 21: iDanceColOut = DANCE_NOTE_PAD2_LEFT; break; case 22: iDanceColOut = DANCE_NOTE_PAD2_UPLEFT; break; case 23: iDanceColOut = DANCE_NOTE_PAD2_DOWN; break; case 24: iDanceColOut = DANCE_NOTE_PAD2_UP; break; case 25: iDanceColOut = DANCE_NOTE_PAD2_UPRIGHT; break; case 26: iDanceColOut = DANCE_NOTE_PAD2_RIGHT; break; default: iDanceColOut = -1; break; } } bool Notes::LoadFromBMSFile( const CString &sPath ) { LOG->WriteLine( "Notes::LoadFromBMSFile( '%s' )", sPath ); // make sure NoteData is loaded ASSERT( m_pNoteData == NULL ); m_pNoteData = new NoteData; int tempNotes[MAX_NOTE_TRACKS][MAX_TAP_NOTE_ROWS]; for( int t=0; t') int iPosOpenBracket = m_sDescription.Find( "<" ); if( iPosOpenBracket == -1 ) iPosOpenBracket = m_sDescription.Find( "(" ); int iPosCloseBracket = m_sDescription.Find( ">" ); if( iPosCloseBracket == -1 ) iPosCloseBracket = m_sDescription.Find( ")" ); if( iPosOpenBracket != -1 && iPosCloseBracket != -1 ) m_sDescription = m_sDescription.Mid( iPosOpenBracket+1, iPosCloseBracket-iPosOpenBracket-1 ); m_sDescription.MakeLower(); LOG->WriteLine( "Notes description found to be '%s'", m_sDescription ); // if there's a 6 in the description, it's probably part of "6panel" or "6-panel" if( m_sDescription.Find("6") != -1 ) m_NotesType = NOTES_TYPE_DANCE_SOLO; } if( -1 != value_name.Find("#playlevel") ) { m_iMeter = atoi( value_data ); } else if( value_name.Left(1) == "#" && IsAnInt( value_name.Mid(1,3) ) && IsAnInt( value_name.Mid(4,2) ) ) // this is step or offset data. Looks like "#00705" { int iMeasureNo = atoi( value_name.Mid(1,3) ); int iTrackNum = atoi( value_name.Mid(4,2) ); CString sNoteData = value_data; CArray arrayNotes; for( int i=0; iWriteLine( "%s:%s: iMeasureNo = %d, iNoteNum = %d, iNumNotesInThisMeasure = %d", // valuename, sNoteData, iMeasureNo, iNoteNum, iNumNotesInThisMeasure ); for( int j=0; j mapDanceNoteToNoteDataColumn; if( m_NotesType == NOTES_TYPE_DANCE_SINGLE ) { mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 1; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 2; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 3; } else if( m_NotesType == NOTES_TYPE_DANCE_DOUBLE ) { mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 1; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 2; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 3; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_LEFT] = 4; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_DOWN] = 5; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_UP] = 6; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_RIGHT] = 7; } else if( m_NotesType == NOTES_TYPE_DANCE_COUPLE ) { mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 1; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 2; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 3; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_LEFT] = 4; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_DOWN] = 5; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_UP] = 6; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_RIGHT] = 7; } else if( m_NotesType == NOTES_TYPE_DANCE_SOLO ) { mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UPLEFT] = 1; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 2; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 3; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UPRIGHT] = 4; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 5; } // copy the tempData we collected into NoteData POSITION pos = mapDanceNoteToNoteDataColumn.GetStartPosition(); while( pos != NULL ) // iterate over all k/v pairs in map { int iTempCol; int iNoteDataCol; mapDanceNoteToNoteDataColumn.GetNextAssoc( pos, iTempCol, iNoteDataCol ); for( int i=0; im_TapNotes[iNoteDataCol][i] = tempNotes[iTempCol][i]; } } m_pNoteData->m_iNumTracks = mapDanceNoteToNoteDataColumn.GetCount(); m_pNoteData->Convert2sAnd3sToHoldNotes(); // search for runs of 32nd notes of all the same note type, and convert them to freezes for( int iCol=0; iColm_TapNotes[iCol][i] == '1' ) // there is a step here { if( iIndexRunStart == -1 ) // we are not in the middle of a run { iIndexRunStart = i; } else // we are in the middle of a run { ; // do nothing. Keep reading in until we hit the end of the run. } } else // there isn't a step here { if( iIndexRunStart == -1 ) // we are not in the middle of a run { ; // do nothing } else // we are in the middle of a run { // this ends the run if( i - iIndexRunStart < 2 ) // reject runs of length 1 { ; // do nothing } else // this run is longer than 1 { // add this HoldNote to the list int iIndexRunEnd = i - 1; HoldNote hn = { iCol, iIndexRunStart, iIndexRunEnd }; m_pNoteData->AddHoldNote( hn ); LOG->WriteLine( "Found a run on track %d: start: %d, end: %d.", hn.m_iTrack, hn.m_iStartIndex, hn.m_iEndIndex ); } iIndexRunStart = -1; // reset so we can look for more runs } } } // done looking for runs } m_DifficultyClass = DifficultyClassFromDescriptionAndMeter( m_sDescription, m_iMeter ); if( m_iMeter == 0 ) { switch( m_DifficultyClass ) { case CLASS_EASY: m_iMeter = 3; break; case CLASS_MEDIUM: m_iMeter = 5; break; case CLASS_HARD: m_iMeter = 8; break; default: ASSERT(0); } } return true; } void DWIcharToNote( char c, InstrumentNumber i, DanceNote ¬e1Out, DanceNote ¬e2Out ) { switch( c ) { case '0': note1Out = DANCE_NOTE_NONE; note2Out = DANCE_NOTE_NONE; break; case '1': note1Out = DANCE_NOTE_PAD1_DOWN; note2Out = DANCE_NOTE_PAD1_LEFT; break; case '2': note1Out = DANCE_NOTE_PAD1_DOWN; note2Out = DANCE_NOTE_NONE; break; case '3': note1Out = DANCE_NOTE_PAD1_DOWN; note2Out = DANCE_NOTE_PAD1_RIGHT; break; case '4': note1Out = DANCE_NOTE_PAD1_LEFT; note2Out = DANCE_NOTE_NONE; break; case '5': note1Out = DANCE_NOTE_NONE; note2Out = DANCE_NOTE_NONE; break; case '6': note1Out = DANCE_NOTE_PAD1_RIGHT; note2Out = DANCE_NOTE_NONE; break; case '7': note1Out = DANCE_NOTE_PAD1_UP; note2Out = DANCE_NOTE_PAD1_LEFT; break; case '8': note1Out = DANCE_NOTE_PAD1_UP; note2Out = DANCE_NOTE_NONE; break; case '9': note1Out = DANCE_NOTE_PAD1_UP; note2Out = DANCE_NOTE_PAD1_RIGHT; break; case 'A': note1Out = DANCE_NOTE_PAD1_UP; note2Out = DANCE_NOTE_PAD1_DOWN; break; case 'B': note1Out = DANCE_NOTE_PAD1_LEFT; note2Out = DANCE_NOTE_PAD1_RIGHT; break; case 'C': note1Out = DANCE_NOTE_PAD1_UPLEFT; note2Out = DANCE_NOTE_NONE; break; case 'D': note1Out = DANCE_NOTE_PAD1_UPRIGHT; note2Out = DANCE_NOTE_NONE; break; case 'E': note1Out = DANCE_NOTE_PAD1_LEFT; note2Out = DANCE_NOTE_PAD1_UPLEFT; break; case 'F': note1Out = DANCE_NOTE_PAD1_UPLEFT; note2Out = DANCE_NOTE_PAD1_DOWN; break; case 'G': note1Out = DANCE_NOTE_PAD1_UPLEFT; note2Out = DANCE_NOTE_PAD1_UP; break; case 'H': note1Out = DANCE_NOTE_PAD1_UPLEFT; note2Out = DANCE_NOTE_PAD1_RIGHT; break; case 'I': note1Out = DANCE_NOTE_PAD1_LEFT; note2Out = DANCE_NOTE_PAD1_UPRIGHT; break; case 'J': note1Out = DANCE_NOTE_PAD1_DOWN; note2Out = DANCE_NOTE_PAD1_UPRIGHT; break; case 'K': note1Out = DANCE_NOTE_PAD1_UP; note2Out = DANCE_NOTE_PAD1_UPRIGHT; break; case 'L': note1Out = DANCE_NOTE_PAD1_UPRIGHT; note2Out = DANCE_NOTE_PAD1_RIGHT; break; case 'M': note1Out = DANCE_NOTE_PAD1_UPLEFT; note2Out = DANCE_NOTE_PAD1_UPRIGHT; break; default: throw RageException( "Encountered invalid DWI note characer '%c'", c ); break; } switch( i ) { case INSTRUMENT_1: break; case INSTRUMENT_2: note1Out <<= 6; if( note2Out != DANCE_NOTE_NONE ) note2Out <<= 6; break; default: ASSERT( false ); } } bool Notes::LoadFromDWITokens( const CString &sMode, const CString &sDescription, const int &iNumFeet, const CString &sStepData1, const CString &sStepData2 ) { LOG->WriteLine( "Notes::LoadFromDWITokens()" ); if( sMode == "#SINGLE" ) m_NotesType = NOTES_TYPE_DANCE_SINGLE; else if( sMode == "#DOUBLE" ) m_NotesType = NOTES_TYPE_DANCE_DOUBLE; else if( sMode == "#COUPLE" ) m_NotesType = NOTES_TYPE_DANCE_COUPLE; else if( sMode == "#SOLO" ) m_NotesType = NOTES_TYPE_DANCE_SOLO; else LOG->WriteLine( "Unrecognized DWI mode '%s'", sMode ); CMap mapDanceNoteToNoteDataColumn; if( m_NotesType == NOTES_TYPE_DANCE_SINGLE ) { mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 1; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 2; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 3; } else if( m_NotesType == NOTES_TYPE_DANCE_DOUBLE || m_NotesType == NOTES_TYPE_DANCE_COUPLE ) { mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 1; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 2; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 3; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_LEFT] = 4; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_DOWN] = 5; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_UP] = 6; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_RIGHT] = 7; } else if( m_NotesType == NOTES_TYPE_DANCE_SOLO ) { mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UPLEFT] = 1; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 2; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 3; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UPRIGHT] = 4; mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 5; } else ASSERT(0); m_sDescription = sDescription; m_iMeter = iNumFeet; m_DifficultyClass = DifficultyClassFromDescriptionAndMeter( m_sDescription, m_iMeter ); m_sCredit = ""; ASSERT( m_pNoteData == NULL ); // if not, then we're loading this Notes twice m_pNoteData = new NoteData; m_pNoteData->m_iNumTracks = mapDanceNoteToNoteDataColumn.GetCount(); for( int pad=0; pad<2; pad++ ) // foreach pad { CString sStepData; switch( pad ) { case 0: sStepData = sStepData1; break; case 1: if( sStepData2 == "" ) // no data continue; // skip sStepData = sStepData2; break; default: ASSERT( false ); } double fCurrentBeat = 0; double fCurrentIncrementer = 1.0/8 * BEATS_PER_MEASURE; for( int i=0; i': fCurrentIncrementer = 1.0/8 * BEATS_PER_MEASURE; break; case ' ': break; // do nothing! case '!': // hold start { // rewind and get the last step we inserted double fLastStepBeat = fCurrentBeat - fCurrentIncrementer; int iIndex = BeatToNoteRow( (float)fLastStepBeat ); char holdChar = sStepData[i++]; DanceNote note1, note2; DWIcharToNote( holdChar, (InstrumentNumber)pad, note1, note2 ); if( note1 != DANCE_NOTE_NONE ) { int iCol1 = mapDanceNoteToNoteDataColumn[note1]; m_pNoteData->m_TapNotes[iCol1][iIndex] = '2'; } if( note2 != DANCE_NOTE_NONE ) { int iCol2 = mapDanceNoteToNoteDataColumn[note2]; m_pNoteData->m_TapNotes[iCol2][iIndex] = '2'; } } break; default: // this is a note character { int iIndex = BeatToNoteRow( (float)fCurrentBeat ); DanceNote note1, note2; DWIcharToNote( c, (InstrumentNumber)pad, note1, note2 ); if( note1 != DANCE_NOTE_NONE ) { int iCol1 = mapDanceNoteToNoteDataColumn[note1]; m_pNoteData->m_TapNotes[iCol1][iIndex] = '1'; } if( note2 != DANCE_NOTE_NONE ) { int iCol2 = mapDanceNoteToNoteDataColumn[note2]; m_pNoteData->m_TapNotes[iCol2][iIndex] = '1'; } fCurrentBeat += fCurrentIncrementer; } break; } } } m_pNoteData->Convert2sAnd3sToHoldNotes(); // this will expand the HoldNote begin markers we wrote into actual HoldNotes ASSERT( m_pNoteData->m_iNumTracks > 0 ); return true; } void Notes::LoadFromSMTokens( const CString &sNotesType, const CString &sDescription, const CString &sCredit, const CString &sDifficultyClass, const CString &sMeter, const CString &sRadarValues, const CString &sNoteData, const bool bLoadNoteData ) { LOG->WriteLine( "Notes::LoadFromSMTokens( %d )", bLoadNoteData ); m_NotesType = StringToNotesType(sNotesType); m_sDescription = sDescription; m_sCredit = sCredit; m_DifficultyClass = StringToDifficultyClass( sDifficultyClass ); m_iMeter = atoi(sMeter); CStringArray saValues; split( sRadarValues, ",", saValues, true ); if( saValues.GetSize() == NUM_RADAR_VALUES ) for( int r=0; r 10 ) m_iMeter = 4; if( m_DifficultyClass == CLASS_INVALID ) m_DifficultyClass = DifficultyClassFromDescriptionAndMeter( sDescription, m_iMeter ); // // Load NoteData // if( !bLoadNoteData ) { SAFE_DELETE( m_pNoteData ); return; // don't continue } // else bLoadNoteData... if( m_pNoteData != NULL ) // already loaded return; // else not already loaded... m_pNoteData = new NoteData(); m_pNoteData->m_iNumTracks = NotesTypeToNumTracks( m_NotesType ); CStringArray asNoteData; split( sNoteData, ",", asNoteData, true ); for( int i=0; im_iNumTracks != sNoteLine.GetLength() ) throw RageException( "Actual number of note columns (%d) is different from the NotesType (%d).", m_pNoteData->m_iNumTracks, sNoteLine.GetLength() ); for( int c=0; cm_TapNotes[c][iIndex] = sNoteLine[c]; } } } void Notes::WriteSMNotesTag( FILE* fp ) { fprintf( fp, "\n//---------------%s - %s----------------\n", NotesTypeToString(m_NotesType), m_sDescription ); fprintf( fp, "#NOTES:\n" ); fprintf( fp, " %s:\n", NotesTypeToString(m_NotesType) ); fprintf( fp, " %s:\n", m_sDescription ); fprintf( fp, " %s:\n", m_sCredit ); fprintf( fp, " %s:\n", DifficultyClassToString(m_DifficultyClass) ); fprintf( fp, " %d:\n", m_iMeter ); CStringArray asRadarValues; for( int r=0; rConvertHoldNotesTo2sAnd3s(); float fLastBeat = m_pNoteData->GetLastBeat(); int iLastMeasure = int( fLastBeat/BEATS_PER_MEASURE ); for( int m=0; m<=iLastMeasure; m++ ) // foreach measure { int iMeasureStartIndex = m * ELEMENTS_PER_MEASURE; int iMeasureLastIndex = (m+1) * ELEMENTS_PER_MEASURE - 1; // probe to find the smallest note type NoteType nt; int iNoteIndexSpacing; for( nt=(NoteType)0; ntIsRowEmpty(i) ) { bFoundSmallerNote = true; break; } } if( bFoundSmallerNote ) continue; // keep searching else break; // stop searching } if( nt == NUM_NOTE_TYPES ) // we didn't find one iNoteIndexSpacing = 1; fprintf( fp, "%d, // measure %d\n", ELEMENTS_PER_MEASURE/iNoteIndexSpacing, m+1 ); for( int i=iMeasureStartIndex; i<=iMeasureLastIndex; i+=iNoteIndexSpacing ) { CString sLineString; for( int c=0; cm_iNumTracks; c++ ) sLineString += m_pNoteData->m_TapNotes[c][i]; fprintf( fp, "%s", sLineString ); if( i <= iMeasureLastIndex-iNoteIndexSpacing ) fprintf( fp, "\n" ); } if( m != iLastMeasure ) fprintf( fp, ",\n" ); } fprintf( fp, ";\n" ); m_pNoteData->Convert2sAnd3sToHoldNotes(); } DifficultyClass Notes::DifficultyClassFromDescriptionAndMeter( CString sDescription, int iMeter ) { sDescription.MakeLower(); const CString sDescriptionParts[NUM_DIFFICULTY_CLASSES][3] = { { "basic", "light", "SDFKSJDKFJS", }, { "another", "trick", "standard", }, { "ssr", "maniac", "heavy", }, }; for( int i=0; iCopyAll( pNewNoteData ); } void Notes::DeleteNoteData() { SAFE_DELETE( m_pNoteData ); } ////////////////////////////////////////////// // ////////////////////////////////////////////// int CompareNotesPointersByMeter(const void *arg1, const void *arg2) { Notes* pNotes1 = *(Notes**)arg1; Notes* pNotes2 = *(Notes**)arg2; int iScore1 = pNotes1->m_iMeter; int iScore2 = pNotes2->m_iMeter; if( iScore1 < iScore2 ) return -1; else if( iScore1 == iScore2 ) return 0; else return 1; } int CompareNotesPointersByDifficultyClass(const void *arg1, const void *arg2) { Notes* pNotes1 = *(Notes**)arg1; Notes* pNotes2 = *(Notes**)arg2; DifficultyClass class1 = pNotes1->m_DifficultyClass; DifficultyClass class2 = pNotes2->m_DifficultyClass; if( class1 < class2 ) return -1; else if( class1 == class2 ) return CompareNotesPointersByMeter( arg1, arg2 ); else return 1; } void SortNotesArrayByDifficultyClass( CArray &arraySteps ) { qsort( arraySteps.GetData(), arraySteps.GetSize(), sizeof(Notes*), CompareNotesPointersByDifficultyClass ); }