void Difficulty::ProcessVSpeeds(TimingData& BPS, TimingData& VerticalSpeeds, double SpeedConstant) { VerticalSpeeds.clear(); if (SpeedConstant) // We're using a CMod, so further processing is pointless { TimingSegment VSpeed; VSpeed.Time = 0; VSpeed.Value = SpeedConstant; VerticalSpeeds.push_back(VSpeed); return; } // Calculate velocity at time based on BPM at time for (auto Time = BPS.begin(); Time != BPS.end(); ++Time) { float VerticalSpeed; TimingSegment VSpeed; if (Time->Value) { float spb = 1 / Time->Value; VerticalSpeed = MeasureBaseSpacing / (spb * 4); } else VerticalSpeed = 0; VSpeed.Value = VerticalSpeed; VSpeed.Time = Time->Time; // We blindly take the BPS time that had offset and drift applied. VerticalSpeeds.push_back(VSpeed); } // Let first speed be not-null. if (VerticalSpeeds.size() && VerticalSpeeds[0].Value == 0) { for (auto i = VerticalSpeeds.begin(); i != VerticalSpeeds.end(); ++i) { if (i->Value != 0) VerticalSpeeds[0].Value = i->Value; } } }
void Difficulty::GetPlayableData(VectorTN NotesOut, TimingData& BPS, TimingData& VerticalSpeeds, TimingData& Warps, float Drift, double SpeedConstant) { /* We'd like to build the notes' position from 0 to infinity, however the real "zero" position would be the judgment line in other words since "up" is negative relative to 0 and 0 is the judgment line position would actually be judgeline - positiveposition and positiveposition would just be measure * measuresize + fraction * fractionsize In practice, since we use a ms-based model rather than a beat one, we just do regular integration of position = sum(speed_i * duration_i) + speed_current * (time_current - speed_start_time) */ assert(Data != nullptr); ProcessBPS(BPS, Drift); ProcessVSpeeds(BPS, VerticalSpeeds, SpeedConstant); if (!SpeedConstant) // If there is a speed constant having speed changes is not what we want ProcessSpeedVariations(BPS, VerticalSpeeds, Drift); if (SpeedConstant) Warps.clear(); else Warps = Data->Warps; // From here on, we'll just copy the notes out. Otherwise, just leave the processed data. if (!NotesOut) return; for (int KeyIndex = 0; KeyIndex < Channels; KeyIndex++) NotesOut[KeyIndex].clear(); /* For all channels of this difficulty */ for (int KeyIndex = 0; KeyIndex < Channels; KeyIndex++) { int MIdx = 0; /* For each measure of this channel */ for (auto Msr = Data->Measures.begin(); Msr != Data->Measures.end(); ++Msr) { /* For each note in the measure... */ ptrdiff_t total_notes = Msr->Notes[KeyIndex].size(); for (auto Note = 0; Note < total_notes; Note++) { /* Calculate position. (Change this to TrackNote instead of processing?) issue is not having the speed change data there. */ NoteData &CurrentNote = (*Msr).Notes[KeyIndex][Note]; TrackNote NewNote; NewNote.AssignNotedata(CurrentNote); NewNote.AddTime(Drift); float VerticalPosition = IntegrateToTime(VerticalSpeeds, NewNote.GetStartTime()); float HoldEndPosition = IntegrateToTime(VerticalSpeeds, NewNote.GetTimeFinal()); // if upscroll change minus for plus as well as matrix at screengameplay7k if (!CurrentNote.EndTime) NewNote.AssignPosition(VerticalPosition); else NewNote.AssignPosition(VerticalPosition, HoldEndPosition); // Okay, now we want to know what fraction of a beat we're dealing with // this way we can display colored (a la Stepmania) notes. // We should do this before changing time by drift. double cBeat = IntegrateToTime(BPS, NewNote.GetStartTime()); double iBeat = floor(cBeat); double dBeat = (cBeat - iBeat); NewNote.AssignFraction(dBeat); double Wamt = -GetWarpAmountAtTime(CurrentNote.StartTime); NewNote.AddTime(Wamt); if (!SpeedConstant || (NewNote.IsJudgable() && !IsWarpingAt(CurrentNote.StartTime))) NotesOut[KeyIndex].push_back(NewNote); } MIdx++; } // done with the channel - sort it std::stable_sort(NotesOut[KeyIndex].begin(), NotesOut[KeyIndex].end(), [](const TrackNote &A, const TrackNote &B) -> bool { return A.GetVertical() < B.GetVertical(); }); } }
void Difficulty::ProcessBPS(TimingData& BPS, double Drift) { /* Calculate BPS. The algorithm is basically the same as VSpeeds. BPS time is calculated applying the offset and drift. */ assert(Data != NULL); TimingData &StopsTiming = Data->Stops; BPS.clear(); for (auto Time = Timing.begin(); Time != Timing.end(); ++Time) { TimingSegment Seg; Seg.Time = TimeFromTimingKind(Timing, StopsTiming, *Time, BPMType, Offset, Drift); Seg.Value = BPSFromTimingKind(Time->Value, BPMType); BPS.push_back(Seg); } /* Sort for justice */ sort(BPS.begin(), BPS.end()); if (!StopsTiming.size() || BPMType != VSRG::Difficulty::BT_BEAT) // Stops only supported in Beat mode. return; /* Here on, just working with stops. */ for (auto Time = StopsTiming.begin(); Time != StopsTiming.end(); ++Time) { TimingSegment Seg; double TValue = TimeAtBeat(Timing, Offset + Drift, Time->Time) + StopTimeAtBeat(StopsTiming, Time->Time); double TValueN = TimeAtBeat(Timing, Offset + Drift, Time->Time) + StopTimeAtBeat(StopsTiming, Time->Time) + Time->Value; /* Initial Stop */ Seg.Time = TValue; Seg.Value = 0; /* First, eliminate collisions. */ for (auto k = BPS.begin(); k != BPS.end();) { if (k->Time == TValue) /* Equal? Remove the collision, leaving only the 0 in front. */ { k = BPS.erase(k); if (k == BPS.end()) break; else continue; } ++k; } // Okay, the collision is out. Let's push our 0-speeder. BPS.push_back(Seg); // Now we find what bps to restore to. float bpsRestore = bps(SectionValue(Timing, Time->Time)); for (auto k = BPS.begin(); k != BPS.end(); ) { if (k->Time > TValue && k->Time <= TValueN) // So wait, there's BPM changes in between? Holy shit. { bpsRestore = k->Value; /* This is the last speed change in the interval that the stop lasts. We'll use it. */ /* Eliminate this since we're not going to use it. */ k = BPS.erase(k); if (k == BPS.end()) break; continue; } ++k; } /* Restored speed after stop */ Seg.Time = TValueN; Seg.Value = bpsRestore; BPS.push_back(Seg); } std::sort(BPS.begin(), BPS.end()); }