void Steps::CalculateRadarValues( float fMusicLengthSeconds ) { // If we're autogen, don't calculate values. GetRadarValues will take from our parent. if( parent != NULL ) return; // Do write radar values, and leave it up to the reading app whether they want to trust // the cached values without recalculating them. /* // If we're an edit, leave the RadarValues invalid. if( IsAnEdit() ) return; */ NoteData tempNoteData; this->GetNoteData( tempNoteData ); FOREACH_PlayerNumber( pn ) m_CachedRadarValues[pn].Zero(); if( tempNoteData.IsComposite() ) { vector<NoteData> vParts; NoteDataUtil::SplitCompositeNoteData( tempNoteData, vParts ); for( size_t pn = 0; pn < min(vParts.size(), size_t(NUM_PLAYERS)); ++pn ) NoteDataUtil::CalculateRadarValues( vParts[pn], fMusicLengthSeconds, m_CachedRadarValues[pn] ); } else { NoteDataUtil::CalculateRadarValues( tempNoteData, fMusicLengthSeconds, m_CachedRadarValues[0] ); fill_n( m_CachedRadarValues + 1, NUM_PLAYERS-1, m_CachedRadarValues[0] ); } }
void Steps::CreateBlank( StepsType ntTo ) { m_StepsType = ntTo; NoteData noteData; noteData.SetNumTracks( GAMEMAN->GetStepsTypeInfo(ntTo).iNumTracks ); this->SetNoteData( noteData ); }
// -1 for iOriginalTracksToTakeFrom means no track void NoteData::LoadTransformed( const NoteData* pOriginal, int iNewNumTracks, const int iOriginalTrackToTakeFrom[] ) { // reset all notes Init(); NoteData Original; Original.To4s( *pOriginal ); Config( Original ); m_iNumTracks = iNewNumTracks; // copy tracks for( int t=0; t<m_iNumTracks; t++ ) { const int iOriginalTrack = iOriginalTrackToTakeFrom[t]; ASSERT_M( iOriginalTrack < Original.m_iNumTracks, ssprintf("from %i >= %i (to %i)", iOriginalTrack, Original.m_iNumTracks, iOriginalTrackToTakeFrom[t])); if( iOriginalTrack == -1 ) continue; m_TapNotes[t] = Original.m_TapNotes[iOriginalTrack]; } Convert4sToHoldNotes(); m_AttackMap = Original.GetAttackMap(); }
void Steps::CalculateRadarValues( float fMusicLengthSeconds ) { // If we're autogen, don't calculate values. GetRadarValues will take from our parent. if( parent != NULL ) return; if( m_bAreCachedRadarValuesJustLoaded ) { m_bAreCachedRadarValuesJustLoaded = false; return; } // Do write radar values, and leave it up to the reading app whether they want to trust // the cached values without recalculating them. /* // If we're an edit, leave the RadarValues invalid. if( IsAnEdit() ) return; */ NoteData tempNoteData; this->GetNoteData( tempNoteData ); FOREACH_PlayerNumber( pn ) m_CachedRadarValues[pn].Zero(); GAMESTATE->SetProcessedTimingData(this->GetTimingData()); if( tempNoteData.IsComposite() ) { vector<NoteData> vParts; NoteDataUtil::SplitCompositeNoteData( tempNoteData, vParts ); for( size_t pn = 0; pn < min(vParts.size(), size_t(NUM_PLAYERS)); ++pn ) NoteDataUtil::CalculateRadarValues( vParts[pn], fMusicLengthSeconds, m_CachedRadarValues[pn] ); } else if (GAMEMAN->GetStepsTypeInfo(this->m_StepsType).m_StepsTypeCategory == StepsTypeCategory_Couple) { NoteData p1 = tempNoteData; // XXX: Assumption that couple will always have an even number of notes. const int tracks = tempNoteData.GetNumTracks() / 2; p1.SetNumTracks(tracks); NoteDataUtil::CalculateRadarValues(p1, fMusicLengthSeconds, m_CachedRadarValues[PLAYER_1]); // at this point, p2 is tempNoteData. NoteDataUtil::ShiftTracks(tempNoteData, tracks); tempNoteData.SetNumTracks(tracks); NoteDataUtil::CalculateRadarValues(tempNoteData, fMusicLengthSeconds, m_CachedRadarValues[PLAYER_2]); } else { NoteDataUtil::CalculateRadarValues( tempNoteData, fMusicLengthSeconds, m_CachedRadarValues[0] ); fill_n( m_CachedRadarValues + 1, NUM_PLAYERS-1, m_CachedRadarValues[0] ); } GAMESTATE->SetProcessedTimingData(NULL); }
void Steps::CopyFrom( Steps* pSource, StepsType ntTo, float fMusicLengthSeconds ) // pSource does not have to be of the same StepsType { m_StepsType = ntTo; NoteData noteData; pSource->GetNoteData( noteData ); noteData.SetNumTracks( GAMEMAN->GetStepsTypeInfo(ntTo).iNumTracks ); parent = NULL; this->SetNoteData( noteData ); this->SetDescription( pSource->GetDescription() ); this->SetDifficulty( pSource->GetDifficulty() ); this->SetMeter( pSource->GetMeter() ); this->CalculateRadarValues( fMusicLengthSeconds ); }
void Steps::GetNoteData( NoteData& noteDataOut ) const { Decompress(); if( m_bNoteDataIsFilled ) { noteDataOut = *m_pNoteData; } else { noteDataOut.ClearAll(); noteDataOut.SetNumTracks( GAMEMAN->GetStepsTypeInfo(m_StepsType).iNumTracks ); } }
void MidiOut::playPart(mid_data &part) { NoteData *nd = createNoteData(); resetPlayData(); event = part.list; tick = part.tempo * part.ippqn; initClock(); while(1) { if(event) { if(playEvent(part, nd)) { // We managed to play the event without having to wait. Move to the next one. event = event->next; } } else { if(loop_num >= 0) { popLoop(); } else if(part.repeat) { // Repeat the part. last_tick = 0; last_time = 0; initClock(); event = part.list; // Handle note-offs? nd->play(); } else { // We played the last event, exit. break; } } if(show_notes) nd->show(part.tempo); endLoopDelay(); } delete nd; }
void KSFLoader::RemoveHoles( NoteData &out, const Song &song ) { /* Start at the second BPM segment; the first one is already aligned. */ for( unsigned seg = 1; seg < song.m_Timing.m_BPMSegments.size(); ++seg ) { // const float FromBeat = song.m_Timing.m_BPMSegments[seg].m_fStartBeat; const float FromBeat = song.m_Timing.m_BPMSegments[seg].m_fStartBeat * song.m_BPMSegments[seg].m_fBPM / song.m_BPMSegments[0].m_fBPM; const int FromRow = (int) BeatToNoteRow(FromBeat); const int ToRow = (int) BeatToNoteRow(song.m_Timing.m_BPMSegments[seg].m_fStartBeat); LOG->Trace("from %f (%i) to (%i)", FromBeat, FromRow, ToRow); // const int ToRow = (int) roundf(FromRow * song.m_Timing.m_BPMSegments[0].m_fBPM / song.m_BPMSegments[seg].m_fBPM); // const int Rows = out.GetLastRow() - FromRow + 1; // int LastRow; // if(seg+1 < song.m_Timing.m_BPMSegments().size()) // LastRow = (int) NoteRowToBeat( song.m_Timing.m_BPMSegments[seg+1].m_fStartBeat ) - 1; // else // LastRow = out.GetLastRow(); NoteData tmp; tmp.SetNumTracks(out.GetNumTracks()); tmp.CopyRange( &out, FromRow, out.GetLastRow() ); out.ClearRange( FromRow, out.GetLastRow() ); out.CopyRange( &tmp, 0, tmp.GetLastRow(), ToRow ); } /* out.ConvertHoldNotesTo2sAnd3s(); for( t = 0; t < notedata.GetNumTracks(); ++t ) { const float CurBPM = song.GetBPMAtBeat( NoteRowToBeat(row) ); song.m_Timing.m_BPMSegments.size() for( int row = 0; row <= notedata.GetLastRow(); ++row ) { TapNote tn = notedata.GetTapNote(t, row); if( tn == TAP_EMPTY ) continue; const int RealRow = (int) roundf(row * OrigBPM / CurBPM); if( RealRow == row ) continue; LOG->Trace("from %i to %i", row, RealRow); notedata.SetTapNote( t, RealRow, tn ); notedata.SetTapNote( t, row, TAP_EMPTY ); } } out.Convert2sAnd3sToHoldNotes(); */ }
/* Return the minimum tap score of a row. If the row isn't complete (not all * taps have been hit), return TNS_None or TNS_Miss. */ TapNoteScore NoteDataWithScoring::MinTapNoteScore( const NoteData &in, unsigned row, PlayerNumber plnum ) { //LOG->Trace("Hey I'm NoteDataWithScoring::MinTapNoteScore"); TapNoteScore score = TNS_W1; for( int t=0; t<in.GetNumTracks(); t++ ) { // Ignore mines (and fake arrows), or the score will always be TNS_None. const TapNote &tn = in.GetTapNote( t, row ); if (tn.type == TapNoteType_Empty || tn.type == TapNoteType_Mine || tn.type == TapNoteType_Fake || tn.type == TapNoteType_AutoKeysound || ( plnum != PlayerNumber_Invalid && tn.pn != plnum ) ) continue; score = min( score, tn.result.tns ); } //LOG->Trace( ssprintf("OMG score is?? %s",TapNoteScoreToString(score).c_str()) ); return score; }
void ScoreKeeperNormal::GetRowCounts( const NoteData &nd, int iRow, int &iNumHitContinueCombo, int &iNumHitMaintainCombo, int &iNumBreakCombo ) { iNumHitContinueCombo = iNumHitMaintainCombo = iNumBreakCombo = 0; for( int track = 0; track < nd.GetNumTracks(); ++track ) { const TapNote &tn = nd.GetTapNote( track, iRow ); if( tn.type != TapNoteType_Tap && tn.type != TapNoteType_HoldHead && tn.type != TapNoteType_Lift ) continue; TapNoteScore tns = tn.result.tns; if( tns >= m_MinScoreToContinueCombo ) ++iNumHitContinueCombo; else if( tns >= m_MinScoreToMaintainCombo ) ++iNumHitMaintainCombo; else ++iNumBreakCombo; } }
void Steps::SetNoteData( const NoteData& noteDataNew ) { ASSERT( noteDataNew.GetNumTracks() == GAMEMAN->GetStepsTypeInfo(m_StepsType).iNumTracks ); DeAutogen( false ); *m_pNoteData = noteDataNew; m_bNoteDataIsFilled = true; m_sNoteDataCompressed = RString(); m_iHash = 0; }
void Steps::SetNoteData( const NoteData& noteDataNew ) { ASSERT( noteDataNew.GetNumTracks() == GAMEMAN->GetStepsTypeInfo(m_StepsType).iNumTracks ); DeAutogen( false ); *m_pNoteData = noteDataNew; m_bNoteDataIsFilled = true; m_sNoteDataCompressed = RString(); m_iHash = 0; m_sFilename = RString(); // We can no longer read from the file because it has changed in memory. }
static StepsType DetermineStepsType( int iPlayer, const NoteData &nd, const RString &sPath ) { ASSERT( NUM_PMS_TRACKS == nd.GetNumTracks() ); bool bTrackHasNote[NUM_NON_AUTO_KEYSOUND_TRACKS]; ZERO( bTrackHasNote ); int iLastRow = nd.GetLastRow(); for( int t=0; t<NUM_NON_AUTO_KEYSOUND_TRACKS; t++ ) { for( int r=0; r<=iLastRow; r++ ) { if( nd.GetTapNote(t, r).type != TapNote::empty ) { bTrackHasNote[t] = true; break; } } } int iNumNonEmptyTracks = 0; for( int t=0; t<NUM_NON_AUTO_KEYSOUND_TRACKS; t++ ) if( bTrackHasNote[t] ) iNumNonEmptyTracks++; switch( iPlayer ) { case 1: switch( iNumNonEmptyTracks ) { case 5: return StepsType_popn_five; case 9: return StepsType_popn_nine; default: return StepsType_Invalid; } default: LOG->UserLog( "Song file", sPath, "has an invalid #PLAYER value %d.", iPlayer ); return StepsType_Invalid; } }
void Style::GetTransformedNoteDataForStyle( PlayerNumber pn, const NoteData& original, NoteData& noteDataOut ) const { ASSERT( pn >=0 && pn <= NUM_PLAYERS ); int iNewToOriginalTrack[MAX_COLS_PER_PLAYER]; for( int col=0; col<m_iColsPerPlayer; col++ ) { ColumnInfo colInfo = m_ColumnInfo[pn][col]; iNewToOriginalTrack[col] = colInfo.track; } noteDataOut.LoadTransformed( original, m_iColsPerPlayer, iNewToOriginalTrack ); }
static void Serialize( const NoteData &o, Json::Value &root ) { root = Json::Value(Json::arrayValue); for(int t=0; t < o.GetNumTracks(); t++ ) { NoteData::TrackMap::const_iterator begin, end; o.GetTapNoteRange( t, 0, MAX_NOTE_ROW, begin, end ); //NoteData::TrackMap tm = o.GetTrack(t); //FOREACHM_CONST( int, TapNote, tm, iter ) for( ; begin != end; ++begin ) { int iRow = begin->first; TapNote tn = begin->second; root.resize( root.size()+1 ); Json::Value &root2 = root[ root.size()-1 ]; root2 = Json::Value(Json::arrayValue); root2.resize(3); root2[(unsigned)0] = NoteRowToBeat(iRow); root2[1] = t; Serialize( tn, root2[2] ); } } }
void GameplayAssist::PlayTicks( const NoteData &nd, const PlayerState *ps ) { using std::max; bool bClap = GAMESTATE->m_SongOptions.GetCurrent().m_bAssistClap; bool bMetronome = GAMESTATE->m_SongOptions.GetCurrent().m_bAssistMetronome; if( !bClap && !bMetronome ) return; // don't play sounds for dead players if( ps->m_HealthState == HealthState_Dead ) return; /* Sound cards have a latency between when a sample is Play()ed and when the sound * will start coming out the speaker. Compensate for this by boosting fPositionSeconds * ahead. This is just to make sure that we request the sound early enough for it to * come out on time; the actual precise timing is handled by SetStartTime. */ SongPosition &position = GAMESTATE->m_pPlayerState[ps->m_PlayerNumber]->m_Position; float fPositionSeconds = position.m_fMusicSeconds; //float fPositionSeconds = GAMESTATE->m_Position.m_fMusicSeconds; fPositionSeconds += SOUNDMAN->GetPlayLatency() + (float)CommonMetrics::TICK_EARLY_SECONDS + 0.250f; const TimingData &timing = *GAMESTATE->m_pCurSteps[ps->m_PlayerNumber]->GetTimingData(); const float fSongBeat = timing.GetBeatFromElapsedTimeNoOffset( fPositionSeconds ); const int iSongRow = max( 0, BeatToNoteRowNotRounded( fSongBeat ) ); static int iRowLastCrossed = -1; if( iSongRow < iRowLastCrossed ) iRowLastCrossed = iSongRow; if( bClap ) { int iClapRow = -1; // for each index we crossed since the last update: FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( nd, r, iRowLastCrossed+1, iSongRow+1 ) if( nd.IsThereATapOrHoldHeadAtRow( r ) ) iClapRow = r; if( iClapRow != -1 && timing.IsJudgableAtRow(iClapRow)) { const float fTickBeat = NoteRowToBeat( iClapRow ); const float fTickSecond = timing.GetElapsedTimeFromBeatNoOffset( fTickBeat ); float fSecondsUntil = fTickSecond - position.m_fMusicSeconds; fSecondsUntil /= GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; /* 2x music rate means the time until the tick is halved */ RageSoundParams p; p.m_StartTime = position.m_LastBeatUpdate + (fSecondsUntil - (float)CommonMetrics::TICK_EARLY_SECONDS); m_soundAssistClap.Play(false, &p); } } if( bMetronome && iRowLastCrossed != -1 ) { //iRowLastCrossed+1, iSongRow+1 int iLastCrossedMeasureIndex; int iLastCrossedBeatIndex; int iLastCrossedRowsRemainder; timing.NoteRowToMeasureAndBeat( iRowLastCrossed, iLastCrossedMeasureIndex, iLastCrossedBeatIndex, iLastCrossedRowsRemainder ); int iCurrentMeasureIndex; int iCurrentBeatIndex; int iCurrentRowsRemainder; timing.NoteRowToMeasureAndBeat( iSongRow, iCurrentMeasureIndex, iCurrentBeatIndex, iCurrentRowsRemainder ); int iMetronomeRow = -1; bool bIsMeasure = false; if( iLastCrossedMeasureIndex != iCurrentMeasureIndex || iLastCrossedBeatIndex != iCurrentBeatIndex ) { iMetronomeRow = iSongRow - iCurrentRowsRemainder; bIsMeasure = iCurrentBeatIndex == 0 && iCurrentRowsRemainder == 0; } if( iMetronomeRow != -1 ) { const float fTickBeat = NoteRowToBeat( iMetronomeRow ); const float fTickSecond = timing.GetElapsedTimeFromBeatNoOffset( fTickBeat ); float fSecondsUntil = fTickSecond - position.m_fMusicSeconds; fSecondsUntil /= GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; /* 2x music rate means the time until the tick is halved */ RageSoundParams p; p.m_StartTime = position.m_LastBeatUpdate + (fSecondsUntil - (float)CommonMetrics::TICK_EARLY_SECONDS); if( bIsMeasure ) m_soundAssistMetronomeMeasure.Play(false, &p); else m_soundAssistMetronomeBeat.Play(false, &p); } } iRowLastCrossed = iSongRow; }
static bool LoadFromBMSFile( const RString &sPath, const NameToData_t &mapNameToData, Steps &out, const MeasureToTimeSig_t &sigAdjustments, const map<RString,int> &idToKeySoundIndex ) { LOG->Trace( "Steps::LoadFromBMSFile( '%s' )", sPath.c_str() ); out.m_StepsType = StepsType_Invalid; // BMS player code. Fill in below and use to determine StepsType. int iPlayer = -1; RString sData; if( GetTagFromMap( mapNameToData, "#player", sData ) ) iPlayer = atoi(sData); if( GetTagFromMap( mapNameToData, "#playlevel", sData ) ) out.SetMeter( atoi(sData) ); NoteData ndNotes; ndNotes.SetNumTracks( NUM_BMS_TRACKS ); /* Read time signatures. Note that these can differ across files in the same * song. */ MeasureToTimeSig_t mapMeasureToTimeSig; ReadTimeSigs( mapNameToData, mapMeasureToTimeSig ); int iHoldStarts[NUM_BMS_TRACKS]; int iHoldPrevs[NUM_BMS_TRACKS]; for( int i = 0; i < NUM_BMS_TRACKS; ++i ) { iHoldStarts[i] = -1; iHoldPrevs[i] = -1; } NameToData_t::const_iterator it; for( it = mapNameToData.lower_bound("#00000"); it != mapNameToData.end(); ++it ) { const RString &sName = it->first; if( sName.size() != 6 || sName[0] != '#' || !IsAnInt( sName.substr(1, 5) ) ) continue; // this is step or offset data. Looks like "#00705" int iMeasureNo = atoi( sName.substr(1,3).c_str() ); int iRawTrackNum = atoi( sName.substr(4,2).c_str() ); int iRowNo = GetMeasureStartRow( mapMeasureToTimeSig, iMeasureNo, sigAdjustments ); float fBeatsPerMeasure = GetBeatsPerMeasure( mapMeasureToTimeSig, iMeasureNo, sigAdjustments ); const RString &sNoteData = it->second; vector<TapNote> vTapNotes; for( size_t i=0; i+1<sNoteData.size(); i+=2 ) { RString sNoteId = sNoteData.substr( i, 2 ); if( sNoteId != "00" ) { vTapNotes.push_back( TAP_ORIGINAL_TAP ); map<RString,int>::const_iterator it = idToKeySoundIndex.find( sNoteId ); if( it != idToKeySoundIndex.end() ) vTapNotes.back().iKeysoundIndex = it->second; } else { vTapNotes.push_back( TAP_EMPTY ); } } const unsigned iNumNotesInThisMeasure = vTapNotes.size(); for( unsigned j=0; j<iNumNotesInThisMeasure; j++ ) { float fPercentThroughMeasure = (float)j/(float)iNumNotesInThisMeasure; int row = iRowNo + lrintf( fPercentThroughMeasure * fBeatsPerMeasure * ROWS_PER_BEAT ); // some BMS files seem to have funky alignment, causing us to write gigantic cache files. // Try to correct for this by quantizing. row = Quantize( row, ROWS_PER_MEASURE/64 ); BmsTrack bmsTrack; bool bIsHold; if( ConvertRawTrackToTapNote(iRawTrackNum, bmsTrack, bIsHold) ) { TapNote &tn = vTapNotes[j]; if( tn.type != TapNote::empty ) { if( bmsTrack == BMS_AUTO_KEYSOUND_1 ) { // shift the auto keysound as far right as possible int iLastEmptyTrack = -1; if( ndNotes.GetTapLastEmptyTrack(row, iLastEmptyTrack) && iLastEmptyTrack >= BMS_AUTO_KEYSOUND_1 ) { tn.type = TapNote::autoKeysound; bmsTrack = (BmsTrack)iLastEmptyTrack; } else { // no room for this note. Drop it. continue; } } else if( bIsHold ) { if( iHoldStarts[bmsTrack] == -1 ) { // Start of a hold. iHoldStarts[bmsTrack] = row; iHoldPrevs[bmsTrack] = row; } else { // We're continuing a hold. iHoldPrevs[bmsTrack] = row; } continue; } } if( iHoldStarts[bmsTrack] != -1 ) { // This is ending a hold. const int iBegin = iHoldStarts[bmsTrack]; const int iEnd = iHoldPrevs[bmsTrack]; if( iBegin < iEnd ) ndNotes.AddHoldNote( bmsTrack, iBegin, iEnd, TAP_ORIGINAL_HOLD_HEAD ); else ndNotes.SetTapNote( bmsTrack, iBegin, TAP_ORIGINAL_TAP ); iHoldStarts[bmsTrack] = -1; iHoldPrevs[bmsTrack] = -1; } // Don't bother inserting empty taps. if( tn.type != TapNote::empty ) ndNotes.SetTapNote( bmsTrack, row, tn ); } } } // We're done reading in all of the BMS values. Time to check for any unfinished holds. for( int iTrack = 0; iTrack < NUM_BMS_TRACKS; ++iTrack ) { const int iBegin = iHoldStarts[iTrack]; const int iEnd = iHoldPrevs[iTrack]; if( iBegin == -1 ) continue; if( iBegin < iEnd ) ndNotes.AddHoldNote( iTrack, iBegin, iEnd, TAP_ORIGINAL_HOLD_HEAD ); else ndNotes.SetTapNote( iTrack, iBegin, TAP_ORIGINAL_TAP ); } out.m_StepsType = DetermineStepsType( iPlayer, ndNotes, sPath ); if( out.m_StepsType == StepsType_beat_single5 && GetTagFromMap( mapNameToData, "#title", sData ) ) { /* Hack: guess at 6-panel. */ // extract the Steps description (looks like 'Music <BASIC>') const size_t iOpenBracket = sData.find_first_of( "<(" ); const size_t iCloseBracket = sData.find_first_of( ">)", iOpenBracket ); // if there's a 6 in the description, it's probably part of "6panel" or "6-panel" if( sData.find('6', iOpenBracket) < iCloseBracket ) out.m_StepsType = StepsType_dance_solo; } if( out.m_StepsType == StepsType_Invalid ) { LOG->UserLog( "Song file", sPath, "has an unknown steps type" ); return false; } // shift all of the autokeysound tracks onto the main tracks for( int t=BMS_AUTO_KEYSOUND_1; t<BMS_AUTO_KEYSOUND_1+NUM_AUTO_KEYSOUND_TRACKS; t++ ) { FOREACH_NONEMPTY_ROW_IN_TRACK( ndNotes, t, row ) { TapNote tn = ndNotes.GetTapNote( t, row ); int iEmptyTrack; if( ndNotes.GetTapFirstEmptyTrack(row, iEmptyTrack) ) { ndNotes.SetTapNote( iEmptyTrack, row, tn ); ndNotes.SetTapNote( t, row, TAP_EMPTY ); } else { LOG->UserLog( "Song file", sPath, "has no room to shift the autokeysound tracks." ); } } }
static StepsType DetermineStepsType( int iPlayer, const NoteData &nd, const RString &sPath ) { ASSERT( NUM_BMS_TRACKS == nd.GetNumTracks() ); bool bTrackHasNote[NUM_NON_AUTO_KEYSOUND_TRACKS]; ZERO( bTrackHasNote ); int iLastRow = nd.GetLastRow(); for( int t=0; t<NUM_NON_AUTO_KEYSOUND_TRACKS; t++ ) { for( int r=0; r<=iLastRow; r++ ) { if( nd.GetTapNote(t, r).type != TapNote::empty ) { bTrackHasNote[t] = true; break; } } } int iNumNonEmptyTracks = 0; for( int t=0; t<NUM_NON_AUTO_KEYSOUND_TRACKS; t++ ) if( bTrackHasNote[t] ) iNumNonEmptyTracks++; switch( iPlayer ) { case 1: // "1 player" /* Track counts: * 4 - dance 4-panel * 5 - pop 5-key * 6 - dance 6-panel, beat 5-key * 8 - beat 7-key * 9 - popn 9-key */ switch( iNumNonEmptyTracks ) { case 4: return StepsType_dance_single; case 5: return StepsType_popn_five; case 6: // FIXME: There's no way to distinguish between these types. // They use the same tracks. Assume it's a Beat type since they // are more common. //return StepsType_dance_solo; return StepsType_beat_single5; case 8: return StepsType_beat_single7; case 9: return StepsType_popn_nine; default: return StepsType_Invalid; } case 2: // couple/battle return StepsType_dance_couple; case 3: // double /* Track counts: * 8 - dance Double * 12 - beat Double 5-key * 16 - beat Double 7-key */ switch( iNumNonEmptyTracks ) { case 8: return StepsType_beat_single7; case 12: return StepsType_beat_double5; case 16: return StepsType_beat_double7; default: return StepsType_Invalid; } default: LOG->UserLog( "Song file", sPath, "has an invalid #PLAYER value %d.", iPlayer ); return StepsType_Invalid; } }
static bool LoadFromKSFFile( const std::string &sPath, Steps &out, Song &song, bool bKIUCompliant ) { using std::max; LOG->Trace( "Steps::LoadFromKSFFile( '%s' )", sPath.c_str() ); MsdFile msd; if( !msd.ReadFile( sPath, false ) ) // don't unescape { LOG->UserLog( "Song file", sPath, "couldn't be opened: %s", msd.GetError().c_str() ); return false; } // this is the value we read for TICKCOUNT int iTickCount = -1; // used to adapt weird tickcounts //float fScrollRatio = 1.0f; -- uncomment when ready to use. vector<std::string> vNoteRows; // According to Aldo_MX, there is a default BPM and it's 60. -aj bool bDoublesChart = false; TimingData stepsTiming; float SMGap1 = 0, SMGap2 = 0, BPM1 = -1, BPMPos2 = -1, BPM2 = -1, BPMPos3 = -1, BPM3 = -1; for( unsigned i=0; i<msd.GetNumValues(); i++ ) { const MsdFile::value_t &sParams = msd.GetValue( i ); std::string sValueName = Rage::make_upper(sParams[0]); /* handle the data...well, not this data: not related to steps. * Skips INTRO, MUSICINTRO, TITLEFILE, DISCFILE, SONGFILE. */ if (sValueName=="TITLE" || Rage::ends_with(sValueName, "INTRO") || Rage::ends_with(sValueName, "FILE") ) { } else if( sValueName=="BPM" ) { BPM1 = StringToFloat(sParams[1]); stepsTiming.AddSegment( BPMSegment(0, BPM1) ); } else if( sValueName=="BPM2" ) { if (bKIUCompliant) { BPM2 = StringToFloat( sParams[1] ); } else { // LOG an error. } } else if( sValueName=="BPM3" ) { if (bKIUCompliant) { BPM3 = StringToFloat( sParams[1] ); } else { // LOG an error. } } else if( sValueName=="BUNKI" ) { if (bKIUCompliant) { BPMPos2 = StringToFloat( sParams[1] ) / 100.0f; } else { // LOG an error. } } else if( sValueName=="BUNKI2" ) { if (bKIUCompliant) { BPMPos3 = StringToFloat( sParams[1] ) / 100.0f; } else { // LOG an error. } } else if( sValueName=="STARTTIME" ) { SMGap1 = -StringToFloat( sParams[1] )/100; stepsTiming.set_offset(SMGap1); } // This is currently required for more accurate KIU BPM changes. else if( sValueName=="STARTTIME2" ) { if (bKIUCompliant) { SMGap2 = -StringToFloat( sParams[1] )/100; } else { // LOG an error. } } else if ( sValueName=="STARTTIME3" ) { // STARTTIME3 only ensures this is a KIU compliant simfile. bKIUCompliant = true; } else if( sValueName=="TICKCOUNT" ) { iTickCount = StringToInt( sParams[1] ); if( iTickCount <= 0 ) { LOG->UserLog( "Song file", sPath, "has an invalid tick count: %d.", iTickCount ); return false; } stepsTiming.AddSegment( TickcountSegment(0, iTickCount)); } else if( sValueName=="DIFFICULTY" ) { out.SetMeter( max(StringToInt(sParams[1]), 1) ); } // new cases from Aldo_MX's fork: else if( sValueName=="PLAYER" ) { std::string sPlayer = Rage::make_lower(sParams[1]); if( sPlayer.find( "double" ) != string::npos ) { bDoublesChart = true; } } // This should always be last. else if( sValueName=="STEP" ) { std::string theSteps = Rage::trim_left(sParams[1]); auto toDump = Rage::split(theSteps, "\n", Rage::EmptyEntries::skip); vNoteRows.insert(vNoteRows.end(), std::make_move_iterator(toDump.begin()), std::make_move_iterator(toDump.end())); } } if( iTickCount == -1 ) { iTickCount = 4; LOG->UserLog( "Song file", sPath, "doesn't have a TICKCOUNT. Defaulting to %i.", iTickCount ); } // Prepare BPM stuff already if the file uses KSF syntax. if( bKIUCompliant ) { if( BPM2 > 0 && BPMPos2 > 0 ) { HandleBunki( stepsTiming, BPM1, BPM2, SMGap1, BPMPos2 ); } if( BPM3 > 0 && BPMPos3 > 0 ) { HandleBunki( stepsTiming, BPM2, BPM3, SMGap2, BPMPos3 ); } } NoteData notedata; // read it into here { std::string sDir, sFName, sExt; splitpath( sPath, sDir, sFName, sExt ); sFName = Rage::make_lower(sFName); out.SetDescription(sFName); // Check another before anything else... is this okay? -DaisuMaster if( sFName.find("another") != string::npos ) { out.SetDifficulty( Difficulty_Edit ); if( !out.GetMeter() ) out.SetMeter( 25 ); } else if(sFName.find("wild") != string::npos || sFName.find("wd") != string::npos || sFName.find("crazy+") != string::npos || sFName.find("cz+") != string::npos || sFName.find("hardcore") != string::npos ) { out.SetDifficulty( Difficulty_Challenge ); if( !out.GetMeter() ) out.SetMeter( 20 ); } else if(sFName.find("crazy") != string::npos || sFName.find("cz") != string::npos || sFName.find("nightmare") != string::npos || sFName.find("nm") != string::npos || sFName.find("crazydouble") != string::npos ) { out.SetDifficulty( Difficulty_Hard ); if( !out.GetMeter() ) out.SetMeter( 14 ); // Set the meters to the Pump scale, not DDR. } else if(sFName.find("hard") != string::npos || sFName.find("hd") != string::npos || sFName.find("freestyle") != string::npos || sFName.find("fs") != string::npos || sFName.find("double") != string::npos ) { out.SetDifficulty( Difficulty_Medium ); if( !out.GetMeter() ) out.SetMeter( 8 ); } else if(sFName.find("easy") != string::npos || sFName.find("ez") != string::npos || sFName.find("normal") != string::npos ) { // I wonder if I should leave easy fall into the Beginner difficulty... -DaisuMaster out.SetDifficulty( Difficulty_Easy ); if( !out.GetMeter() ) out.SetMeter( 4 ); } else if(sFName.find("beginner") != string::npos || sFName.find("practice") != string::npos || sFName.find("pr") != string::npos ) { out.SetDifficulty( Difficulty_Beginner ); if( !out.GetMeter() ) out.SetMeter( 4 ); } else { out.SetDifficulty( Difficulty_Hard ); if( !out.GetMeter() ) out.SetMeter( 10 ); } out.m_StepsType = StepsType_pump_single; // Check for "halfdouble" before "double". if(sFName.find("halfdouble") != string::npos || sFName.find("half-double") != string::npos || sFName.find("h_double") != string::npos || sFName.find("hdb") != string::npos ) out.m_StepsType = StepsType_pump_halfdouble; // Handle bDoublesChart from above as well. -aj else if(sFName.find("double") != string::npos || sFName.find("nightmare") != string::npos || sFName.find("freestyle") != string::npos || sFName.find("db") != string::npos || sFName.find("nm") != string::npos || sFName.find("fs") != string::npos || bDoublesChart ) out.m_StepsType = StepsType_pump_double; else if( sFName.find("_1") != string::npos ) out.m_StepsType = StepsType_pump_single; else if( sFName.find("_2") != string::npos ) out.m_StepsType = StepsType_pump_couple; } switch( out.m_StepsType ) { case StepsType_pump_single: notedata.SetNumTracks( 5 ); break; case StepsType_pump_couple: notedata.SetNumTracks( 10 ); break; case StepsType_pump_double: notedata.SetNumTracks( 10 ); break; case StepsType_pump_routine: notedata.SetNumTracks( 10 ); break; // future files may have this? case StepsType_pump_halfdouble: notedata.SetNumTracks( 6 ); break; default: FAIL_M( fmt::sprintf("%i", out.m_StepsType) ); } int t = 0; int iHoldStartRow[13]; for( t=0; t<13; t++ ) iHoldStartRow[t] = -1; bool bTickChangeNeeded = false; int newTick = -1; float fCurBeat = 0.0f; float prevBeat = 0.0f; // Used for hold tails. for (auto &sRowString: vNoteRows) { sRowString = Rage::trim_right(sRowString, "\r\n"); if( sRowString == "" ) { continue; // skip } // All 2s indicates the end of the song. if( sRowString == "2222222222222" ) { // Finish any holds that didn't get...well, finished. for( t=0; t < notedata.GetNumTracks(); t++ ) { if( iHoldStartRow[t] != -1 ) // this ends the hold { if( iHoldStartRow[t] == BeatToNoteRow(prevBeat) ) { notedata.SetTapNote( t, iHoldStartRow[t], TAP_ORIGINAL_TAP ); } else { notedata.AddHoldNote(t, iHoldStartRow[t], BeatToNoteRow(prevBeat), TAP_ORIGINAL_HOLD_HEAD ); } } } /* have this row be the last moment in the song, unless * a future step ends later. */ //float curTime = stepsTiming.GetElapsedTimeFromBeat(fCurBeat); //if (curTime > song.GetSpecifiedLastSecond()) //{ // song.SetSpecifiedLastSecond(curTime); //} song.SetSpecifiedLastSecond( song.GetSpecifiedLastSecond() + 4 ); break; } else if( Rage::starts_with(sRowString, "|") ) { /* if (bKIUCompliant) { // Log an error, ignore the line. continue; } */ // gotta do something tricky here: if the bpm is below one then a couple of calculations // for scrollsegments will be made, example, bpm 0.2, tick 4000, the scrollsegment will // be 0. if the tickcount is non a stepmania standard then it will be adapted, a scroll // segment will then be added based on approximations. -DaisuMaster // eh better do it considering the tickcount (high tickcounts) // I'm making some experiments, please spare me... //continue; std::string temp = sRowString.substr(2,sRowString.size()-3); float numTemp = StringToFloat(temp); if (Rage::starts_with(sRowString, "|T")) { // duh iTickCount = static_cast<int>(numTemp); // I have been owned by the man -DaisuMaster stepsTiming.SetTickcountAtBeat( fCurBeat, Rage::clamp(iTickCount, 0, ROWS_PER_BEAT) ); } else if (Rage::starts_with(sRowString, "|B")) { // BPM stepsTiming.SetBPMAtBeat( fCurBeat, numTemp ); } else if (Rage::starts_with(sRowString, "|E")) { // DelayBeat float fCurDelay = 60 / stepsTiming.GetBPMAtBeat(fCurBeat) * numTemp / iTickCount; fCurDelay += stepsTiming.GetDelayAtRow(BeatToNoteRow(fCurBeat) ); stepsTiming.SetDelayAtBeat( fCurBeat, fCurDelay ); } else if (Rage::starts_with(sRowString, "|D")) { // Delays float fCurDelay = stepsTiming.GetStopAtRow(BeatToNoteRow(fCurBeat) ); fCurDelay += numTemp / 1000; stepsTiming.SetDelayAtBeat( fCurBeat, fCurDelay ); } else if (Rage::starts_with(sRowString, "|M") || Rage::starts_with(sRowString, "|C")) { // multipliers/combo ComboSegment seg( BeatToNoteRow(fCurBeat), int(numTemp) ); stepsTiming.AddSegment( seg ); } else if (Rage::starts_with(sRowString, "|S")) { // speed segments } else if (Rage::starts_with(sRowString, "|F")) { // fakes } else if (Rage::starts_with(sRowString, "|X")) { // scroll segments ScrollSegment seg = ScrollSegment( BeatToNoteRow(fCurBeat), numTemp ); stepsTiming.AddSegment( seg ); //return true; } continue; } // Half-doubles is offset; "0011111100000". if( out.m_StepsType == StepsType_pump_halfdouble ) sRowString.erase( 0, 2 ); // Update TICKCOUNT for Direct Move files. if( bTickChangeNeeded ) { iTickCount = newTick; bTickChangeNeeded = false; } for( t=0; t < notedata.GetNumTracks(); t++ ) { if( sRowString[t] == '4' ) { /* Remember when each hold starts; ignore the middle. */ if( iHoldStartRow[t] == -1 ) iHoldStartRow[t] = BeatToNoteRow(fCurBeat); continue; } if( iHoldStartRow[t] != -1 ) // this ends the hold { int iEndRow = BeatToNoteRow(prevBeat); if( iHoldStartRow[t] == iEndRow ) notedata.SetTapNote( t, iHoldStartRow[t], TAP_ORIGINAL_TAP ); else { //notedata.AddHoldNote( t, iHoldStartRow[t], iEndRow , TAP_ORIGINAL_PUMP_HEAD ); notedata.AddHoldNote( t, iHoldStartRow[t], iEndRow , TAP_ORIGINAL_HOLD_HEAD ); } iHoldStartRow[t] = -1; } TapNote tap; switch( sRowString[t] ) { case '0': tap = TAP_EMPTY; break; case '1': tap = TAP_ORIGINAL_TAP; break; //allow setting more notetypes on ksf files, this may come in handy (it should) -DaisuMaster case 'M': case 'm': tap = TAP_ORIGINAL_MINE; break; case 'F': case 'f': tap = TAP_ORIGINAL_FAKE; break; case 'L': case 'l': tap = TAP_ORIGINAL_LIFT; break; default: LOG->UserLog( "Song file", sPath, "has an invalid row \"%s\"; corrupt notes ignored.", sRowString.c_str() ); //return false; tap = TAP_EMPTY; break; } notedata.SetTapNote(t, BeatToNoteRow(fCurBeat), tap); } prevBeat = fCurBeat; fCurBeat = prevBeat + 1.0f / iTickCount; } out.SetNoteData( notedata ); out.m_Timing = stepsTiming; out.TidyUpData(); out.SetSavedToDisk( true ); // we're loading from disk, so this is by definintion already saved return true; }
void getNoteList(vector<char260>& song, vector<NoteData>& songdata, double mindur, int tonic) { songdata.resize(0); NoteData tempnote; int melstart = -1; int melstop = -1; int i, j; int octave = 0; int degree = 0; int accidental = 0; double duration = mindur; int bar = 0; // int tuplet = 0; int major[8] = {-1, 0, 6, 12, 17, 23, 29, 35}; // int oldstate = -1; int state = -1; int nextstate = -1; int phend = 0; int phnum = 0; int phstart = 0; int slend = 0; int slstart = 0; int tie = 0; getLineRange(song, "MEL", melstart, melstop); for (i=melstart; i<=melstop; i++) { if (song[i].c[0] == '\0' || song[i].c[1] == '\0' || song[i].c[2] == '\0' || song[i].c[3] == '\0') { cout << "Error: invalid line in MEL[]: " << song[i].c << endl; exit(1); } j = 4; phstart = 1; phend = 0; // Note Format: (+|-)*[0..7]_*\.*( )? // ONADB // Order of data: Octave, Note, Accidental, Duration, Barline #define STATE_SLSTART -1 #define STATE_OCTAVE 0 #define STATE_NOTE 1 #define STATE_ACC 2 #define STATE_DUR 3 #define STATE_BAR 4 #define STATE_SLEND 5 while (j < 200 && song[i].c[j] != '\0') { // oldstate = state; switch (song[i].c[j]) { // Octave information: case '-': octave--; state = STATE_OCTAVE; break; case '+': octave++; state = STATE_OCTAVE; break; // Duration information: case '_': duration *= 2.0; state = STATE_DUR; break; case '.': duration *= 1.5; state = STATE_DUR; break; // Accidental information: case 'b': accidental--; state = STATE_ACC; break; case '#': accidental++; state = STATE_ACC; break; // Note information: case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': degree = major[song[i].c[j] - '0']; state = STATE_NOTE; break; case 'O': degree = major[0]; state = STATE_NOTE; break; // Barline information: case ' ': state = STATE_BAR; if (song[i].c[j+1] == ' ') { bar = 1; } break; // Other information: case '{': slstart = 1; state = STATE_SLSTART; break; case '}': slend = 1; state = STATE_SLEND; break; // case '(': tuplet = 1; break; // case ')': tuplet = 0; break; case '/': break; case ']': break; // case '>': break; // unknown marker // case '<': break; // case '^': tie = 1; state = STATE_NOTE; break; default : cout << "Error: unknown character " << song[i].c[j] << " on the line: " << song[i].c << endl; exit(1); } j++; switch (song[i].c[j]) { case '-': case '+': nextstate = STATE_OCTAVE; break; case 'O': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': nextstate = STATE_NOTE; break; case 'b': case '#': nextstate = STATE_ACC; break; case '_': case '.': nextstate = STATE_DUR; break; case '{': nextstate = STATE_SLSTART; break; case '}': nextstate = STATE_SLEND; break; case '^': nextstate = STATE_NOTE; break; case ' ': if (song[i].c[j+1] == ' ') nextstate = STATE_BAR; else if (song[i].c[j+1] == '/') nextstate = -2; break; case '\0': phend = 1; default: nextstate = -1; } if (nextstate < state || ((nextstate == STATE_NOTE) && (state == nextstate))) { tempnote.clear(); if (degree < 0) { // rest tempnote.pitch = -999; } else { tempnote.pitch = degree + 40*(octave + 4) + accidental + tonic; } if (tie) { tempnote.pitch = songdata[(int)songdata.size()-1].pitch; if (songdata[(int)songdata.size()-1].tieend) { songdata[(int)songdata.size()-1].tiecont = 1; songdata[(int)songdata.size()-1].tieend = 0; } else { songdata[(int)songdata.size()-1].tiestart = 1; } tempnote.tieend = 1; } tempnote.duration = duration; tempnote.phend = phend; tempnote.bar = bar; tempnote.phstart = phstart; tempnote.slstart = slstart; tempnote.slend = slend; if (nextstate == -2) { tempnote.bar = 2; tempnote.phend = 1; } tempnote.phnum = phnum; songdata.push_back(tempnote); duration = mindur; degree = 0; bar = 0; tie = 0; phend = 0; phstart = 0; slend = 0; slstart = 0; octave = 0; accidental = 0; if (nextstate == -2) { return; } } } phnum++; } }
void NoteDataWithScoring::GetActualRadarValues(const NoteData &in, const PlayerStageStats &pss, float song_seconds, RadarValues& out) { using std::max; // Anybody editing this function should also examine // NoteDataUtil::CalculateRadarValues to make sure it handles things the // same way. // Some of this logic is similar or identical to // NoteDataUtil::CalculateRadarValues because I couldn't figure out a good // way to combine them into one. -Kyz PlayerNumber pn= pss.m_player_number; garv_state state; NoteData::all_tracks_const_iterator curr_note= in.GetTapNoteRangeAllTracks(0, MAX_NOTE_ROW); TimingData* timing= GAMESTATE->GetProcessedTimingData(); // first_hittable_row and last_hittable_row exist so that // GetActualVoltageRadarValue can be passed the correct song length. // GetActualVoltageRadarValue scores based on the max combo, a full combo // is a full voltage score. The song length is used instead of trying to // figure out the max combo for the song because rolls mean there isn't a // limit to the max combo. -Kyz int first_hittable_row= -1; int last_hittable_row= -1; bool tick_holds= GAMESTATE->GetCurrentGame()->m_bTickHolds; while(!curr_note.IsAtEnd()) { if(curr_note.Row() != state.curr_row) { DoRowEndRadarActualCalc(state); state.curr_row= curr_note.Row(); state.num_notes_on_curr_row= 0; state.num_holds_on_curr_row= 0; state.judgable= timing->IsJudgableAtRow(state.curr_row); for(size_t n= 0; n < state.hold_ends.size(); ++n) { if(state.hold_ends[n].end_row < state.curr_row) { state.hold_ends.erase(state.hold_ends.begin() + n); --n; } } state.last_tns_on_row= TapNoteScore_Invalid; state.last_time_on_row= -9999; state.worst_tns_on_row= TapNoteScore_Invalid; } bool for_this_player= curr_note->pn == pn || pn == PLAYER_INVALID || curr_note->pn == PLAYER_INVALID; if(state.judgable && for_this_player) { switch(curr_note->type) { case TapNoteType_HoldTail: // If there are tick holds, then the hold tail needs to be counted // in last_hittable_row because that's where the combo will end. // -Kyz if(tick_holds) { UpdateHittable(state.curr_row, first_hittable_row, last_hittable_row); } break; case TapNoteType_Tap: case TapNoteType_HoldHead: // HoldTails and Attacks are counted by IsTap. But it doesn't // make sense to count HoldTails as hittable notes. -Kyz case TapNoteType_Attack: case TapNoteType_Lift: UpdateHittable(state.curr_row, first_hittable_row, last_hittable_row); ++state.num_notes_on_curr_row; state.notes_hit_for_stream+= (curr_note->result.tns >= state.stream_tns); state.notes_hit+= (curr_note->result.tns >= state.taps_tns); if(curr_note->result.tns < state.worst_tns_on_row) { state.worst_tns_on_row= curr_note->result.tns; } if(curr_note->result.fTapNoteOffset > state.last_time_on_row) { state.last_time_on_row= curr_note->result.fTapNoteOffset; state.last_tns_on_row= curr_note->result.tns; } if(curr_note->type == TapNoteType_HoldHead) { if(curr_note->subType == TapNoteSubType_Hold) { state.holds_held+= (curr_note->HoldResult.hns == HNS_Held); } else if(curr_note->subType == TapNoteSubType_Roll) { state.rolls_held+= (curr_note->HoldResult.hns == HNS_Held); } state.hold_ends.push_back( hold_status(state.curr_row + curr_note->iDuration, curr_note->HoldResult.iLastHeldRow)); ++state.num_holds_on_curr_row; } else if(curr_note->type == TapNoteType_Lift) { state.lifts_hit+= (curr_note->result.tns >= state.lifts_tns); } break; case TapNoteType_Mine: state.mines_avoided+= (curr_note->result.tns == TNS_AvoidMine); break; case TapNoteType_Fake: default: break; } } ++curr_note; } DoRowEndRadarActualCalc(state); // ScreenGameplay passes in the RadarValues that were calculated by // NoteDataUtil::CalculateRadarValues, so those are reused here. -Kyz int note_count = static_cast<int>(out[RadarCategory_Notes]); int jump_count = static_cast<int>(out[RadarCategory_Jumps]); int hold_count = static_cast<int>(out[RadarCategory_Holds]); float hittable_steps_length = max(0.f, timing->GetElapsedTimeFromBeat(NoteRowToBeat(last_hittable_row)) - timing->GetElapsedTimeFromBeat(NoteRowToBeat(first_hittable_row))); // The for loop and the assert are used to ensure that all fields of // RadarValue get set in here. FOREACH_ENUM(RadarCategory, rc) { switch(rc) { case RadarCategory_Stream: out[rc]= Rage::clamp(float(state.notes_hit_for_stream) / note_count, 0.0f, 1.0f); break; case RadarCategory_Voltage: out[rc]= GetActualVoltageRadarValue(in, hittable_steps_length, pss); break; case RadarCategory_Air: out[rc]= Rage::clamp(float(state.jumps_hit_for_air) / jump_count, 0.0f, 1.0f); break; case RadarCategory_Freeze: out[rc]= Rage::clamp(float(state.holds_held) / hold_count, 0.0f, 1.0f); break; case RadarCategory_Chaos: out[rc]= GetActualChaosRadarValue(in, song_seconds, pss); break; case RadarCategory_TapsAndHolds: out[rc] = static_cast<float>(state.taps_hit); break; case RadarCategory_Jumps: out[rc] = static_cast<float>(state.jumps_hit); break; case RadarCategory_Holds: out[rc] = static_cast<float>(state.holds_held); break; case RadarCategory_Mines: out[rc] = static_cast<float>(state.mines_avoided); break; case RadarCategory_Hands: out[rc] = static_cast<float>(state.hands_hit); break; case RadarCategory_Rolls: out[rc] = static_cast<float>(state.rolls_held); break; case RadarCategory_Lifts: out[rc] = static_cast<float>(state.lifts_hit); break; case RadarCategory_Fakes: out[rc] = out[rc]; break; case RadarCategory_Notes: out[rc] = static_cast<float>(state.notes_hit); break; DEFAULT_FAIL(rc); } } }
static void WriteDWINotesField( RageFile &f, const Steps &out, int start ) { NoteData notedata; out.GetNoteData( notedata ); NoteDataUtil::InsertHoldTails( notedata ); const int iLastMeasure = int( notedata.GetLastBeat()/BEATS_PER_MEASURE ); for( int m=0; m<=iLastMeasure; m++ ) // foreach measure { NoteType nt = NoteDataUtil::GetSmallestNoteTypeForMeasure( notedata, m ); double fCurrentIncrementer = 0; switch( nt ) { case NOTE_TYPE_4TH: case NOTE_TYPE_8TH: fCurrentIncrementer = 1.0/8 * BEATS_PER_MEASURE; break; case NOTE_TYPE_12TH: case NOTE_TYPE_24TH: f.Write( "[" ); fCurrentIncrementer = 1.0/24 * BEATS_PER_MEASURE; break; case NOTE_TYPE_16TH: f.Write( "(" ); fCurrentIncrementer = 1.0/16 * BEATS_PER_MEASURE; break; case NOTE_TYPE_32ND: case NOTE_TYPE_64TH: f.Write( "{" ); fCurrentIncrementer = 1.0/64 * BEATS_PER_MEASURE; break; case NOTE_TYPE_48TH: case NOTE_TYPE_192ND: case NoteType_Invalid: // since, for whatever reason, the only way to do // 48ths is through a block of 192nds... f.Write( "`" ); fCurrentIncrementer = 1.0/192 * BEATS_PER_MEASURE; break; default: ASSERT_M(0, ssprintf("nt = %d",nt) ); break; } double fFirstBeatInMeasure = m * BEATS_PER_MEASURE; double fLastBeatInMeasure = (m+1) * BEATS_PER_MEASURE; for( double b=fFirstBeatInMeasure; b<=fLastBeatInMeasure-1/64.0f; b+=fCurrentIncrementer ) // need the -0.0001 to account for rounding errors { int row = BeatToNoteRow( (float)b ); RString str; switch( out.m_StepsType ) { case StepsType_dance_single: case StepsType_dance_couple: case StepsType_dance_double: str = NotesToDWIString( notedata.GetTapNote(start+0, row), notedata.GetTapNote(start+1, row), notedata.GetTapNote(start+2, row), notedata.GetTapNote(start+3, row) ); // Blank out the notes so we don't write them again if the incrementer is small notedata.SetTapNote(start+0, row, TAP_EMPTY); notedata.SetTapNote(start+1, row, TAP_EMPTY); notedata.SetTapNote(start+2, row, TAP_EMPTY); notedata.SetTapNote(start+3, row, TAP_EMPTY); break; case StepsType_dance_solo: str = NotesToDWIString( notedata.GetTapNote(0, row), notedata.GetTapNote(1, row), notedata.GetTapNote(2, row), notedata.GetTapNote(3, row), notedata.GetTapNote(4, row), notedata.GetTapNote(5, row) ); // Blank out the notes so we don't write them again if the incrementer is small notedata.SetTapNote(start+0, row, TAP_EMPTY); notedata.SetTapNote(start+1, row, TAP_EMPTY); notedata.SetTapNote(start+2, row, TAP_EMPTY); notedata.SetTapNote(start+3, row, TAP_EMPTY); notedata.SetTapNote(start+4, row, TAP_EMPTY); notedata.SetTapNote(start+5, row, TAP_EMPTY); break; default: ASSERT(0); // not a type supported by DWI. We shouldn't have called in here if that's the case } f.Write( str ); } switch( nt ) { case NOTE_TYPE_4TH: case NOTE_TYPE_8TH: break; case NOTE_TYPE_12TH: case NOTE_TYPE_24TH: f.Write( "]" ); break; case NOTE_TYPE_16TH: f.Write( ")" ); break; case NOTE_TYPE_32ND: case NOTE_TYPE_64TH: f.Write( "}" ); break; case NOTE_TYPE_48TH: case NOTE_TYPE_192ND: case NoteType_Invalid: f.Write( "'" ); break; default: ASSERT(0); // fall though } f.PutLine( "" ); } }
static NoteData ParseNoteData(RString &step1, RString &step2, Steps &out, const RString &path) { g_mapDanceNoteToNoteDataColumn.clear(); switch( out.m_StepsType ) { case StepsType_dance_single: g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0; g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 1; g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 2; g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 3; break; case StepsType_dance_double: case StepsType_dance_couple: g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0; g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 1; g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 2; g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 3; g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_LEFT] = 4; g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_DOWN] = 5; g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_UP] = 6; g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD2_RIGHT] = 7; break; case StepsType_dance_solo: g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_LEFT] = 0; g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UPLEFT] = 1; g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_DOWN] = 2; g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UP] = 3; g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_UPRIGHT] = 4; g_mapDanceNoteToNoteDataColumn[DANCE_NOTE_PAD1_RIGHT] = 5; break; DEFAULT_FAIL( out.m_StepsType ); } NoteData newNoteData; newNoteData.SetNumTracks( g_mapDanceNoteToNoteDataColumn.size() ); for( int pad=0; pad<2; pad++ ) // foreach pad { RString sStepData; switch( pad ) { case 0: sStepData = step1; break; case 1: if( step2 == "" ) // no data continue; // skip sStepData = step2; break; DEFAULT_FAIL( pad ); } sStepData.Replace("\n", ""); sStepData.Replace("\r", ""); sStepData.Replace("\t", ""); sStepData.Replace(" ", ""); double fCurrentBeat = 0; double fCurrentIncrementer = 1.0/8 * BEATS_PER_MEASURE; for( size_t i=0; i<sStepData.size(); ) { char c = sStepData[i++]; switch( c ) { // begins a series case '(': fCurrentIncrementer = 1.0/16 * BEATS_PER_MEASURE; break; case '[': fCurrentIncrementer = 1.0/24 * BEATS_PER_MEASURE; break; case '{': fCurrentIncrementer = 1.0/64 * BEATS_PER_MEASURE; break; case '`': fCurrentIncrementer = 1.0/192 * BEATS_PER_MEASURE; break; // ends a series case ')': case ']': case '}': case '\'': case '>': fCurrentIncrementer = 1.0/8 * BEATS_PER_MEASURE; break; default: // this is a note character { if( c == '!' ) { LOG->UserLog( "Song file", path, "has an unexpected character: '!'." ); continue; } bool jump = false; if( c == '<' ) { /* Arr. Is this a jump or a 1/192 marker? */ if( Is192( sStepData, i ) ) { fCurrentIncrementer = 1.0/192 * BEATS_PER_MEASURE; break; } /* It's a jump. * We need to keep reading notes until we hit a >. */ jump = true; i++; } const int iIndex = BeatToNoteRow( (float)fCurrentBeat ); i--; do { c = sStepData[i++]; if( jump && c == '>' ) break; int iCol1, iCol2; DWIcharToNoteCol( c, (GameController)pad, iCol1, iCol2, path ); if( iCol1 != -1 ) newNoteData.SetTapNote(iCol1, iIndex, TAP_ORIGINAL_TAP); if( iCol2 != -1 ) newNoteData.SetTapNote(iCol2, iIndex, TAP_ORIGINAL_TAP); if(i>=sStepData.length()) { break; //we ran out of data //while looking for the ending > mark } if( sStepData[i] == '!' ) { i++; const char holdChar = sStepData[i++]; DWIcharToNoteCol(holdChar, (GameController)pad, iCol1, iCol2, path ); if( iCol1 != -1 ) newNoteData.SetTapNote(iCol1, iIndex, TAP_ORIGINAL_HOLD_HEAD); if( iCol2 != -1 ) newNoteData.SetTapNote(iCol2, iIndex, TAP_ORIGINAL_HOLD_HEAD); } } while( jump ); fCurrentBeat += fCurrentIncrementer; } break; } } } /* Fill in iDuration. */ for( int t=0; t<newNoteData.GetNumTracks(); ++t ) { FOREACH_NONEMPTY_ROW_IN_TRACK( newNoteData, t, iHeadRow ) { TapNote tn = newNoteData.GetTapNote( t, iHeadRow ); if( tn.type != TapNote::hold_head ) continue; int iTailRow = iHeadRow; bool bFound = false; while( !bFound && newNoteData.GetNextTapNoteRowForTrack(t, iTailRow) ) { const TapNote &TailTap = newNoteData.GetTapNote( t, iTailRow ); if( TailTap.type == TapNote::empty ) continue; newNoteData.SetTapNote( t, iTailRow, TAP_EMPTY ); tn.iDuration = iTailRow - iHeadRow; newNoteData.SetTapNote( t, iHeadRow, tn ); bFound = true; } if( !bFound ) { /* The hold was never closed. */ LOG->UserLog("Song file", path, "failed to close a hold note in \"%s\" on track %i", DifficultyToString(out.GetDifficulty()).c_str(), t); newNoteData.SetTapNote( t, iHeadRow, TAP_EMPTY ); } } }
void VibratoTimeAxis::doUpdate() { Channel *active = gdata->getActiveChannel(); int myStartChunk = -1; int myCurrentChunk = -1; int myEndChunk = -1; double myNoteLength = 0.0; int myWindowOffset = -999999; if (active) { AnalysisData *data = active->dataAtCurrentChunk(); if(data && active->isVisibleNote(data->getNoteIndex()) && active->isLabelNote(data->getNoteIndex())) { NoteData *note = new NoteData(); note = &active->noteData[data->getNoteIndex()]; myStartChunk = note->startChunk(); myCurrentChunk = active->chunkAtCurrentTime(); myEndChunk = note->endChunk(); myNoteLength = note->noteLength(); // Calculate windowoffset if ((myEndChunk - myStartChunk) * zoomFactorX > width() - 2 * noteLabelOffset) { // The vibrato-polyline doesn't fit in the window if ((myCurrentChunk - myStartChunk) * zoomFactorX < (width() - 2 * noteLabelOffset)/2) { // We're at the left side of the vibrato-polyline myWindowOffset = 0 - noteLabelOffset; } else if ((myEndChunk - myCurrentChunk) * zoomFactorX < (width() - 2 * noteLabelOffset)/2) { // We're at the right side of the vibrato-polyline myWindowOffset = toInt((myEndChunk - myStartChunk) * zoomFactorX - width() + noteLabelOffset + 1); } else { // We're somewhere in the middle of the vibrato-polyline myWindowOffset = toInt((myCurrentChunk - myStartChunk) * zoomFactorX - width()/2); } } else { // The vibrato-polyline does fit in the window myWindowOffset = 0 - noteLabelOffset; } } } if (myCurrentChunk == -1) { // No note if (prevCurrentChunk == myCurrentChunk) { // Still no timeaxis needed, no update needed } else { // Timeaxis should be erased, update widget prevCurrentChunk = -1; prevWindowOffset = -999999; currentChunkToUse = -1; } } else { // Note if (prevWindowOffset == myWindowOffset) { // No movement, don't redraw timeaxis } else { // Position in note to draw has changed, so draw the timeaxis prevCurrentChunk = myCurrentChunk; prevWindowOffset = myWindowOffset; startChunkToUse = myStartChunk; currentChunkToUse = myCurrentChunk; endChunkToUse = myEndChunk; noteLengthToUse = myNoteLength; windowOffsetToUse = myWindowOffset; } } }
bool KSFLoader::LoadFromKSFFile( const CString &sPath, Steps &out, const Song &song ) { LOG->Trace( "Steps::LoadFromKSFFile( '%s' )", sPath.c_str() ); MsdFile msd; if( !msd.ReadFile( sPath ) ) RageException::Throw( "Error opening file '%s'.", sPath.c_str() ); int iTickCount = -1; // this is the value we read for TICKCOUNT CStringArray asRows; for( unsigned i=0; i<msd.GetNumValues(); i++ ) { const MsdFile::value_t &sParams = msd.GetValue(i); CString sValueName = sParams[0]; // handle the data if( 0==stricmp(sValueName,"TICKCOUNT") ) iTickCount = atoi(sParams[1]); else if( 0==stricmp(sValueName,"STEP") ) { CString step = sParams[1]; TrimLeft(step); split( step, "\n", asRows, true ); } else if( 0==stricmp(sValueName,"DIFFICULTY") ) out.SetMeter(atoi(sParams[1])); } if( iTickCount == -1 ) { iTickCount = 2; LOG->Warn( "\"%s\": TICKCOUNT not found; defaulting to %i", sPath.c_str(), iTickCount ); } NoteData notedata; // read it into here { CString sDir, sFName, sExt; splitpath( sPath, sDir, sFName, sExt ); sFName.MakeLower(); out.SetDescription(sFName); if( sFName.Find("crazy")!=-1 ) { out.SetDifficulty(DIFFICULTY_HARD); if(!out.GetMeter()) out.SetMeter(8); } else if( sFName.Find("hard")!=-1 ) { out.SetDifficulty(DIFFICULTY_MEDIUM); if(!out.GetMeter()) out.SetMeter(5); } else if( sFName.Find("easy")!=-1 ) { out.SetDifficulty(DIFFICULTY_EASY); if(!out.GetMeter()) out.SetMeter(2); } else { out.SetDifficulty(DIFFICULTY_MEDIUM); if(!out.GetMeter()) out.SetMeter(5); } notedata.SetNumTracks( 5 ); out.m_StepsType = STEPS_TYPE_PUMP_SINGLE; /* Check for "halfdouble" before "double". */ if( sFName.Find("halfdouble") != -1 || sFName.Find("h_double") != -1 ) { notedata.SetNumTracks( 6 ); out.m_StepsType = STEPS_TYPE_PUMP_HALFDOUBLE; } else if( sFName.Find("double") != -1 ) { notedata.SetNumTracks( 10 ); out.m_StepsType = STEPS_TYPE_PUMP_DOUBLE; } else if( sFName.Find("_2") != -1 ) { notedata.SetNumTracks( 10 ); out.m_StepsType = STEPS_TYPE_PUMP_COUPLE; } } int iHoldStartRow[13]; int t; for( t=0; t<13; t++ ) iHoldStartRow[t] = -1; for( unsigned r=0; r<asRows.size(); r++ ) { CString& sRowString = asRows[r]; StripCrnl( sRowString ); if( sRowString == "" ) continue; // skip /* All 2s indicates the end of the song. */ if( sRowString == "2222222222222" ) break; if(sRowString.size() != 13) { LOG->Warn("File %s had a RowString with an improper length (\"%s\"); corrupt notes ignored", sPath.c_str(), sRowString.c_str()); return false; } /* Half-doubles is offset; "0011111100000". */ if( out.m_StepsType == STEPS_TYPE_PUMP_HALFDOUBLE ) sRowString.erase( 0, 2 ); // the length of a note in a row depends on TICKCOUNT float fBeatThisRow = r/(float)iTickCount; int row = BeatToNoteRow(fBeatThisRow); for( int t=0; t < notedata.GetNumTracks(); t++ ) { if( sRowString[t] == '4' ) { /* Remember when each hold starts; ignore the middle. */ if( iHoldStartRow[t] == -1 ) iHoldStartRow[t] = r; continue; } if( iHoldStartRow[t] != -1 ) // this ends the hold { HoldNote hn ( t, /* button */ BeatToNoteRow(iHoldStartRow[t]/(float)iTickCount), /* start */ BeatToNoteRow((r-1)/(float)iTickCount) /* end */ ); notedata.AddHoldNote( hn ); iHoldStartRow[t] = -1; } TapNote tap; switch(sRowString[t]) { case '0': tap = TAP_EMPTY; break; case '1': tap = TAP_ORIGINAL_TAP; break; default: ASSERT(0); tap = TAP_EMPTY; break; } notedata.SetTapNote(t, row, tap); } } /* We need to remove holes where the BPM increases. */ // if( song.m_Timing.m_BPMSegments.size() > 1 ) // RemoveHoles( notedata, song ); out.SetNoteData(¬edata); out.TidyUpData(); return true; }