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 BPStoSPB(TimingData &BPS) { auto BPSCopy = BPS; for (auto i = BPS.begin(); i != BPS.end(); ++i) { double valueBPS = i->Value; i->Value = 1 / valueBPS; i->Time = IntegrateToTime(BPSCopy, i->Time); // Find time in beats based off beats in time } }
void Difficulty::ProcessSpeedVariations(TimingData& BPS, TimingData& VerticalSpeeds, double Drift) { assert(Data != NULL); TimingData tVSpeeds = VerticalSpeeds; // We need this to store what values to change TimingData &Scrolls = Data->Scrolls; std::sort(Scrolls.begin(), Scrolls.end()); for (TimingData::const_iterator Change = Scrolls.begin(); Change != Scrolls.end(); ++Change) { TimingData::const_iterator NextChange = (Change + 1); double ChangeTime = Change->Time + Drift + Offset; /* Find all VSpeeds if there exists a speed change which is virtually happening at the same time as this VSpeed modify it to be this value * factor */ bool MoveOn = false; for (auto Time = VerticalSpeeds.begin(); Time != VerticalSpeeds.end(); ++Time) { if (abs(ChangeTime - Time->Time) < 0.00001) { Time->Value *= Change->Value; MoveOn = true; } } if (MoveOn) continue; /* There are no collisions- insert a new speed at this time */ if (ChangeTime < 0) continue; float SpeedValue; SpeedValue = SectionValue(tVSpeeds, ChangeTime) * Change->Value; TimingSegment VSpeed; VSpeed.Time = ChangeTime; VSpeed.Value = SpeedValue; VerticalSpeeds.push_back(VSpeed); /* Theorically, if there were a VSpeed change after this one (such as a BPM change) we've got to modify them if they're between this and the next speed change. Apparently, this behaviour is a "bug" since osu!mania resets SV changes after a BPM change. */ if (BPMType == VSRG::Difficulty::BT_BEATSPACE) // Okay, we're an osu!mania chart, leave the resetting. continue; // We're not an osu!mania chart, so it's time to do what should be done. for (auto Time = VerticalSpeeds.begin(); Time != VerticalSpeeds.end(); ++Time) { if (Time->Time > ChangeTime) { // Two options, between two speed changes, or the last one. Second case, NextChange == Scrolls.end(). // Otherwise, just move on // Last speed change if (NextChange == Scrolls.end()) { Time->Value = Change->Value * SectionValue(tVSpeeds, Time->Time); } else { if (Time->Time < NextChange->Time) // Between speed changes Time->Value = Change->Value * SectionValue(tVSpeeds, Time->Time); } } } } std::sort(VerticalSpeeds.begin(), VerticalSpeeds.end()); }
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()); }