void UpdateTiming() { long numSamples; int tempOut; if (!playback_.GetNumPlayedSamples(out numSamples, out tempOut)) { numSamples = -1; } isNearChanged_ = false; isJustChanged_ = false; currentSample_ = (int)numSamples; if (currentSample_ >= 0) { just_.Bar = (int)(currentSample_ / samplesPerBar_); just_.Beat = (int)((currentSample_ - just_.Bar * samplesPerBar_) / samplesPerBeat_); just_.Unit = (int)((currentSample_ - just_.Bar * samplesPerBar_ - just_.Beat * samplesPerBeat_) / samplesPerUnit_); just_.Fix(); if (numBlockBar_ > 0) { while (just_.Bar >= numBlockBar_) { just_--; } } timeSecFromJust_ = (double)(currentSample_ - just_.Bar * samplesPerBar_ - just_.Beat * samplesPerBeat_ - just_.Unit * samplesPerUnit_) / (double)SamplingRate; isFormerHalf_ = (timeSecFromJust_ * SamplingRate) < samplesPerUnit_ / 2; near_.Copy(just_); if (!isFormerHalf_) { near_++; near_.LoopBack(numBlockBar_); } isJustChanged_ = (just_.Equals(oldJust_) == false); isNearChanged_ = (near_.Equals(oldNear_) == false); CallEvents(); oldNear_.Copy(near_); oldJust_.Copy(just_); } if (DebugText != null) { DebugText.text = "Just = " + Just.ToString() + ", MusicalTime = " + MusicalTime_; if (BlockInfos.Count > 0) { DebugText.text += System.Environment.NewLine + "block[" + currentBlockIndex_ + "] = " + CurrentBlock_.BlockName + "(" + numBlockBar_ + "bar)"; } } }
/// <summary> /// dif from timing to Just on musical time unit. /// </summary> /// <param name="timing"></param> /// <returns></returns> public static float MusicalTimeFrom(Timing timing) { int index = 0; for (int i = 0; i < SectionCount; ++i) { if (i + 1 < SectionCount) { if (timing < Current_[i + 1].StartTiming) { index = i; break; } } else { index = i; } } int startIndex = Mathf.Min(index, Current_.sectionIndex_); int endIndex = Mathf.Max(index, Current_.sectionIndex_); Timing currentTiming = new Timing(timing < Just ? timing : Just); Timing endTiming = (timing > Just ? timing : Just); int musicalTime = 0; for (int i = startIndex; i <= endIndex; ++i) { if (i < endIndex) { musicalTime += Current_[i + 1].StartTiming.GetMusicalTime(Current_[i]) - currentTiming.GetMusicalTime(Current_[i]); currentTiming.Copy(Current_[i + 1].StartTiming); } else { musicalTime += endTiming.GetMusicalTime(Current_[i]) - currentTiming.GetMusicalTime(Current_[i]); } } return((float)((timing > Just ? -1 : 1) * musicalTime + TimeSecFromJust / MusicTimeUnit)); }
void UpdateTiming() { // find section index int newIndex = sectionIndex_; if (CreateSectionClips) { newIndex = sectionSources_.IndexOf(musicSource_); currentSample_ = musicSource_.timeSamples; } else { int oldSample = currentSample_; currentSample_ = musicSource_.timeSamples; if (sectionIndex_ + 1 >= Sections.Count) { if (currentSample_ < oldSample) { newIndex = 0; } } else { if (Sections[sectionIndex_ + 1].StartTimeSamples <= currentSample_) { newIndex = sectionIndex_ + 1; } } } if (newIndex != sectionIndex_) { sectionIndex_ = newIndex; OnSectionChanged(); } // calc current timing isNearChanged_ = false; isJustChanged_ = false; int sectionSample = currentSample_ - (CreateSectionClips ? 0 : CurrentSection_.StartTimeSamples); if (sectionSample >= 0) { just_.Bar = (int)(sectionSample / samplesPerBar_) + CurrentSection_.StartBar; just_.Beat = (int)((sectionSample % samplesPerBar_) / samplesPerBeat_); just_.Unit = (int)(((sectionSample % samplesPerBar_) % samplesPerBeat_) / samplesPerUnit_); just_.Fix(CurrentSection_); if (CreateSectionClips) { if (CurrentSection_.LoopType == Section.ClipType.Loop && numLoopBar_ > 0) { just_.Bar -= CurrentSection_.StartBar; while (just_.Bar >= numLoopBar_) { just_.Decrement(CurrentSection_); } just_.Bar += CurrentSection_.StartBar; } if (isTransitioning_ && just_.Equals(transitionTiming_)) { if (CurrentSection_.LoopType == Section.ClipType.Loop && just_.Bar == CurrentSection_.StartBar) { just_.Bar = CurrentSection_.StartBar + numLoopBar_; } just_.Decrement(CurrentSection_); } } else { if (sectionIndex_ + 1 >= Sections.Count) { if (numLoopBar_ > 0) { while (just_.Bar >= numLoopBar_) { just_.Decrement(CurrentSection_); } } } else { while (just_.Bar >= Sections[sectionIndex_ + 1].StartBar) { just_.Decrement(CurrentSection_); } } } just_.Bar -= CurrentSection_.StartBar; timeSecFromJust_ = (double)(sectionSample - just_.Bar * samplesPerBar_ - just_.Beat * samplesPerBeat_ - just_.Unit * samplesPerUnit_) / (double)samplingRate_; isFormerHalf_ = (timeSecFromJust_ * samplingRate_) < samplesPerUnit_ / 2; just_.Bar += CurrentSection_.StartBar; near_.Copy(just_); if (!isFormerHalf_) { near_.Increment(CurrentSection_); } if (samplesInLoop_ != 0 && currentSample_ + samplesPerUnit_ / 2 >= samplesInLoop_) { near_.Init(); } isNearChanged_ = (near_.Equals(oldNear_) == false); isJustChanged_ = (just_.Equals(oldJust_) == false); CallEvents(); oldNear_.Copy(near_); oldJust_.Copy(just_); } if (DebugText != null) { DebugText.text = "Just = " + Just.ToString() + ", MusicalTime = " + MusicalTime_; if (Sections.Count > 0) { DebugText.text += System.Environment.NewLine + "section[" + sectionIndex_ + "] = " + CurrentSection_.ToString(); } } else if (DebugPrint) { string text = "Just = " + Just.ToString() + ", MusicalTime = " + MusicalTime_; if (Sections.Count > 0) { text += System.Environment.NewLine + "section[" + sectionIndex_ + "] = " + CurrentSection_.ToString(); } Debug.Log(text); } }
void SetNextSection_(int sectionIndex, SyncType syncType = SyncType.NextBar) { if (CreateSectionClips == false || isTransitioning_) { return; } if (sectionIndex < 0 || SectionCount <= sectionIndex || sectionIndex == sectionIndex_) { return; } int syncUnit = 0; transitionTiming_.Copy(just_); switch (syncType) { case SyncType.NextBeat: syncUnit = samplesPerBeat_; transitionTiming_.Beat += 1; transitionTiming_.Unit = 0; break; case SyncType.Next2Beat: syncUnit = samplesPerBeat_ * 2; transitionTiming_.Beat += 2; transitionTiming_.Unit = 0; break; case SyncType.NextBar: syncUnit = samplesPerBar_; transitionTiming_.Bar += 1; transitionTiming_.Beat = transitionTiming_.Unit = 0; break; case SyncType.Next2Bar: syncUnit = samplesPerBar_ * 2; transitionTiming_.Bar += 2; transitionTiming_.Beat = transitionTiming_.Unit = 0; break; case SyncType.Next4Bar: syncUnit = samplesPerBar_ * 4; transitionTiming_.Bar += 4; transitionTiming_.Beat = transitionTiming_.Unit = 0; break; case SyncType.Next8Bar: syncUnit = samplesPerBar_ * 8; transitionTiming_.Bar += 8; transitionTiming_.Beat = transitionTiming_.Unit = 0; break; case SyncType.SectionEnd: syncUnit = samplesInLoop_; transitionTiming_.Bar = CurrentSection_.StartBar + numLoopBar_; transitionTiming_.Beat = transitionTiming_.Unit = 0; break; } transitionTiming_.Fix(CurrentSection_); if (CurrentSection_.LoopType == Section.ClipType.Loop && transitionTiming_.Bar >= CurrentSection_.StartBar + numLoopBar_) { transitionTiming_.Bar -= numLoopBar_; } if (syncUnit <= 0) { return; } double transitionTime = AudioSettings.dspTime + (syncUnit - musicSource_.timeSamples % syncUnit) / (double)samplingRate_ / musicSource_.pitch; sectionSources_[sectionIndex].PlayScheduled(transitionTime); sectionSources_[sectionIndex_].SetScheduledEndTime(transitionTime); isTransitioning_ = true; }
// Update is called once per frame void Update() { long numSamples; isNowChanged_ = false; isJustChanged_ = false; #if ADX if (playback.GetStatus() != CriAtomExPlayback.Status.Playing) { return; } int tempOut; if (!playback.GetNumPlayedSamples(out numSamples, out tempOut)) { numSamples = -1; } #else if (!MusicSource.IsPlaying()) { return; } numSamples = MusicSource.source.timeSamples; #endif if (numSamples >= 0) { Just_.bar = (int)(numSamples / SamplesPerBar); #if ADX UpdateNumBlockBar(numSamples); if (NumBlockBar != 0) { Just_.bar %= NumBlockBar; } #else if (NumBar != 0) { Just_.bar %= NumBar; } #endif Just_.beat = (int)((numSamples % SamplesPerBar) / SamplesPerBeat); Just_.unit = (int)((numSamples % SamplesPerBeat) / SamplesPerUnit); isFormerHalf_ = (numSamples % SamplesPerUnit) < SamplesPerUnit / 2; dtFromJust_ = (double)(numSamples % SamplesPerUnit) / (double)SamplingRate; Now_.Copy(Just_); if (!isFormerHalf_) { Now_.Increment(); } if (SamplesInLoop != 0 && numSamples + SamplesPerUnit / 2 >= SamplesInLoop) { Now_.Init(); } isNowChanged_ = Now_.totalUnit != OldNow.totalUnit; isJustChanged_ = Just_.totalUnit != OldJust.totalUnit; CallEvents(); OldNow.Copy(Now_); OldJust.Copy(Just_); } else { //Debug.LogWarning( "Warning!! Failed to GetNumPlayedSamples" ); } }
/// <summary> /// Does a procedure similar to <see cref="MapCleaner"/> which adjusts the pattern so it fits in the beatmap. /// It does so according to the options selected in this. /// </summary> /// <param name="patternBeatmap"></param> /// <param name="beatmap"></param> /// <param name="parts"></param> /// <param name="timingPointsChanges"></param> private void PreparePattern(Beatmap patternBeatmap, Beatmap beatmap, out List <Part> parts, out List <TimingPointsChange> timingPointsChanges) { double patternStartTime = patternBeatmap.GetHitObjectStartTime(); Timing originalTiming = beatmap.BeatmapTiming; Timing patternTiming = patternBeatmap.BeatmapTiming; GameMode targetMode = (GameMode)beatmap.General["Mode"].IntValue; double originalCircleSize = beatmap.Difficulty["CircleSize"].DoubleValue; double patternCircleSize = patternBeatmap.Difficulty["CircleSize"].DoubleValue; double originalTickRate = beatmap.Difficulty["SliderTickRate"].DoubleValue; double patternTickRate = patternBeatmap.Difficulty["SliderTickRate"].DoubleValue; // Don't include SV changes if it is based on nothing bool includePatternSliderVelocity = patternTiming.Count > 0; // Avoid including hitsounds if there are no timingpoints to get hitsounds from bool includeTimingPointHitsounds = IncludeHitsounds && patternTiming.Count > 0; // Don't scale to new timing if the pattern has no timing to speak of bool scaleToNewTiming = ScaleToNewTiming && patternTiming.Redlines.Count > 0; // Avoid overwriting timing if the pattern has no redlines TimingOverwriteMode timingOverwriteMode = patternTiming.Redlines.Count > 0 ? TimingOverwriteMode : TimingOverwriteMode.OriginalTimingOnly; // Get the scale for custom scale x CS scale double csScale = Beatmap.GetHitObjectRadius(originalCircleSize) / Beatmap.GetHitObjectRadius(patternCircleSize); double spatialScale = ScaleToNewCircleSize && !double.IsNaN(csScale) ? CustomScale * csScale : CustomScale; // Get a BPM multiplier to fix the tick rate // This multiplier is not meant to change SV so this is subtracted from the greenline SV later double bpmMultiplier = FixTickRate ? patternTickRate / originalTickRate : 1; // Dont give new combo to all hit objects which were actually new combo in the pattern, // because it leads to unexpected NC's at the start of patterns. // Collect Kiai toggles List <TimingPoint> kiaiToggles = new List <TimingPoint>(); bool lastKiai = false; // If not including the kiai of the pattern, add the kiai of the original map. // This has to be done because this part of the original map might get deleted. foreach (TimingPoint tp in IncludeKiai ? patternTiming.TimingPoints : originalTiming.TimingPoints) { if (tp.Kiai != lastKiai || kiaiToggles.Count == 0) { kiaiToggles.Add(tp.Copy()); lastKiai = tp.Kiai; } } // Collect SliderVelocity changes for mania/taiko List <TimingPoint> svChanges = new List <TimingPoint>(); double lastSV = -100; // If not including the SV of the pattern, add the SV of the original map. // This has to be done because this part of the original map might get deleted. foreach (TimingPoint tp in includePatternSliderVelocity ? patternTiming.TimingPoints : originalTiming.TimingPoints) { if (tp.Uninherited) { lastSV = -100; } else { if (Math.Abs(tp.MpB - lastSV) > Precision.DOUBLE_EPSILON) { svChanges.Add(tp.Copy()); lastSV = tp.MpB; } } } // If not including the SV of the pattern, set the SV of sliders to that of the original beatmap, // so the pattern will take over the SV of the original beatmap. if (!includePatternSliderVelocity) { foreach (var ho in patternBeatmap.HitObjects.Where(ho => ho.IsSlider)) { ho.SliderVelocity = originalTiming.GetSvAtTime(ho.Time); } } // Get the timeline before moving all objects so it has the correct hitsounds // Make sure that moving the objects in the pattern moves the timeline objects aswell // This method is NOT safe to use in beat time Timeline patternTimeline = patternBeatmap.GetTimeline(); Timing transformOriginalTiming = originalTiming; Timing transformPatternTiming = patternTiming; if (scaleToNewTiming) { // Transform everything to beat time relative to pattern start time foreach (var ho in patternBeatmap.HitObjects) { double oldEndTime = ho.GetEndTime(false); ho.Time = patternTiming.GetBeatLength(patternStartTime, ho.Time); ho.EndTime = patternTiming.GetBeatLength(patternStartTime, oldEndTime); // The body hitsounds are not copies of timingpoints in patternTiming so they should be copied before changing offset for (int i = 0; i < ho.BodyHitsounds.Count; i++) { TimingPoint tp = ho.BodyHitsounds[i].Copy(); tp.Offset = patternTiming.GetBeatLength(patternStartTime, tp.Offset); ho.BodyHitsounds[i] = tp; } } foreach (var tp in kiaiToggles.Concat(svChanges)) { tp.Offset = patternTiming.GetBeatLength(patternStartTime, tp.Offset); } // Transform the pattern redlines to beat time // This will not change the order of redlines (unless negative BPM exists) transformPatternTiming = patternTiming.Copy(); foreach (var tp in transformPatternTiming.Redlines) { tp.Offset = patternTiming.GetBeatLength(patternStartTime, tp.Offset); } // Transform the original timingpoints to beat time // This will not change the order of timingpoints (unless negative BPM exists) transformOriginalTiming = originalTiming.Copy(); foreach (var tp in transformOriginalTiming.TimingPoints) { tp.Offset = originalTiming.GetBeatLength(patternStartTime, tp.Offset); } } // Fix SV for the new global SV var globalSvFactor = transformOriginalTiming.SliderMultiplier / transformPatternTiming.SliderMultiplier; if (FixGlobalSv) { foreach (HitObject ho in patternBeatmap.HitObjects.Where(o => o.IsSlider)) { ho.SliderVelocity *= globalSvFactor; } foreach (TimingPoint tp in svChanges) { tp.MpB *= globalSvFactor; } } else { foreach (HitObject ho in patternBeatmap.HitObjects.Where(o => o.IsSlider)) { ho.TemporalLength /= globalSvFactor; } } // Partition the pattern based on the timing in the pattern if (PatternOverwriteMode == PatternOverwriteMode.PartitionedOverwrite) { parts = PartitionBeatmap(patternBeatmap, scaleToNewTiming); } else { parts = new List <Part> { new Part(patternBeatmap.HitObjects[0].Time, patternBeatmap.HitObjects[patternBeatmap.HitObjects.Count - 1].Time, patternBeatmap.HitObjects) }; } // Construct a new timing which is a mix of the beatmap and the pattern. // If scaleToNewTiming then use beat relative values to determine the duration of timing sections in the pattern. // scaleToNewTiming must scale all the partitions, timingpoints, hitobjects, and events (if applicable). Timing newTiming = new Timing(transformOriginalTiming.SliderMultiplier); var lastEndTime = double.NegativeInfinity; foreach (var part in parts) { var startTime = part.StartTime; var endTime = part.EndTime; // Subtract one to omit BPM changes right on the end of the part. // Add the redlines in between patterns newTiming.AddRange(transformOriginalTiming.GetRedlinesInRange(lastEndTime, startTime, false)); var startOriginalRedline = transformOriginalTiming.GetRedlineAtTime(startTime); // Minus 1 the offset so its possible to have a custom BPM redline right on the start time if you have // the default BPM redline before it. var patternDefaultMpb = transformPatternTiming.GetMpBAtTime(startTime - 2 * Precision.DOUBLE_EPSILON); TimingPoint[] inPartRedlines; TimingPoint startPartRedline; switch (timingOverwriteMode) { case TimingOverwriteMode.PatternTimingOnly: // Subtract one from the end time to omit BPM changes right on the end of the part. inPartRedlines = transformPatternTiming.GetRedlinesInRange(startTime, Math.Max(startTime, endTime - 2 * Precision.DOUBLE_EPSILON)).ToArray(); startPartRedline = transformPatternTiming.GetRedlineAtTime(startTime); break; case TimingOverwriteMode.InPatternAbsoluteTiming: var tempInPartRedlines = transformPatternTiming.GetRedlinesInRange(startTime, endTime - 2 * Precision.DOUBLE_EPSILON); // Replace all parts in the pattern which have the default BPM to timing from the target beatmap. inPartRedlines = tempInPartRedlines.Select(tp => { if (Precision.AlmostEquals(tp.MpB, patternDefaultMpb)) { var tp2 = transformOriginalTiming.GetRedlineAtTime(tp.Offset).Copy(); tp2.Offset = tp2.Offset; return(tp2); } return(tp); }).ToArray(); startPartRedline = startOriginalRedline; break; case TimingOverwriteMode.InPatternRelativeTiming: // Multiply mix the pattern timing and the original timing together. // The pattern timing divided by the default BPM will be used as a scalar for the original timing. var tempInPartRedlines2 = transformPatternTiming.GetRedlinesInRange(startTime, endTime - 2 * Precision.DOUBLE_EPSILON); var tempInOriginalRedlines = transformOriginalTiming.GetRedlinesInRange(startTime, endTime - 2 * Precision.DOUBLE_EPSILON); // Replace all parts in the pattern which have the default BPM to timing from the target beatmap. inPartRedlines = tempInPartRedlines2.Select(tp => { var tp2 = tp.Copy(); tp2.MpB *= transformOriginalTiming.GetMpBAtTime(tp.Offset) / patternDefaultMpb; return(tp2); }).Concat(tempInOriginalRedlines.Select(tp => { var tp2 = tp.Copy(); tp2.MpB *= transformPatternTiming.GetMpBAtTime(tp.Offset) / patternDefaultMpb; return(tp2); })).ToArray(); startPartRedline = transformPatternTiming.GetRedlineAtTime(startTime).Copy(); startPartRedline.MpB *= transformOriginalTiming.GetMpBAtTime(startTime) / patternDefaultMpb; break; default: // Original timing only // Subtract one from the end time to omit BPM changes right on the end of the part. inPartRedlines = transformOriginalTiming.GetRedlinesInRange(startTime, Math.Max(startTime, endTime - 2 * Precision.DOUBLE_EPSILON)).ToArray(); startPartRedline = transformOriginalTiming.GetRedlineAtTime(startTime); break; } // Add the redlines for inside the part newTiming.AddRange(inPartRedlines); // If the pattern starts with different BPM than the map add an extra redline at the start of the pattern // to make sure it the pattern starts out at the right BPM as we only copy the timingpoints during the pattern itself // and the redline may be way before that. // This will probably only do something on the PatternTimingOnly mode as the other modes make sure // the BPM at the start of the pattern will be the same as the original beatmap anyways. if (Math.Abs(startPartRedline.MpB * bpmMultiplier - startOriginalRedline.MpB) > Precision.DOUBLE_EPSILON) { // We dont have to add the redline again if its already during the pattern. if (Math.Abs(startPartRedline.Offset - startTime) > Precision.DOUBLE_EPSILON) { var copy = startPartRedline.Copy(); copy.Offset = startTime; newTiming.Add(copy); } } // Fix SV for the new BPM, so the SV effect of the new BPM is cancelled if (FixBpmSv) { if (scaleToNewTiming) { foreach (HitObject ho in patternBeatmap.HitObjects.Where(o => o.IsSlider)) { var bpmSvFactor = SnapToNewTiming ? transformPatternTiming.GetMpBAtTime(ho.Time) / newTiming.GetMpBAtTime(newTiming.ResnapBeatTime(ho.Time, BeatDivisors)) : transformPatternTiming.GetMpBAtTime(ho.Time) / newTiming.GetMpBAtTime(ho.Time); ho.SliderVelocity *= bpmSvFactor; } } else { foreach (HitObject ho in patternBeatmap.HitObjects.Where(o => o.IsSlider)) { var bpmSvFactor = SnapToNewTiming ? transformPatternTiming.GetMpBAtTime(ho.Time) / newTiming.GetMpBAtTime(newTiming.Resnap(ho.Time, BeatDivisors)) : transformPatternTiming.GetMpBAtTime(ho.Time) / newTiming.GetMpBAtTime(ho.Time); ho.SliderVelocity *= bpmSvFactor; } } } // Recalculate temporal length and re-assign redline for the sliderend resnapping later foreach (var ho in part.HitObjects) { ho.UnInheritedTimingPoint = newTiming.GetRedlineAtTime(ho.Time); if (ho.IsSlider) { // If scaleToNewTiming then the end time is already at the correct beat time // The SV has to be adjusted so the sliderend is really on the end time if (scaleToNewTiming) { var wantedMsDuration = (newTiming.GetMilliseconds(ho.GetEndTime(false), patternStartTime) - newTiming.GetMilliseconds(ho.Time, patternStartTime)) / ho.Repeat; var trueMsDuration = newTiming.CalculateSliderTemporalLength(SnapToNewTiming ? newTiming.ResnapBeatTime(ho.Time, BeatDivisors) : ho.Time, ho.PixelLength, ho.SliderVelocity); ho.SliderVelocity /= trueMsDuration / wantedMsDuration; } else { ho.TemporalLength = newTiming.CalculateSliderTemporalLength(SnapToNewTiming ? newTiming.Resnap(ho.Time, BeatDivisors) : ho.Time, ho.PixelLength, ho.SliderVelocity); } } } // Update the end time because the lengths of sliders changed endTime = part.HitObjects.Max(o => o.GetEndTime(!scaleToNewTiming)); part.EndTime = endTime; // Add a redline at the end of the pattern to make sure the BPM goes back to normal after the pattern. var endOriginalRedline = transformOriginalTiming.GetRedlineAtTime(endTime); var endPartRedline = inPartRedlines.LastOrDefault() ?? startPartRedline; if (Math.Abs(endPartRedline.MpB * bpmMultiplier - endOriginalRedline.MpB) > Precision.DOUBLE_EPSILON) { // We dont have to add the redline again if its already during the parts in between parts. if (Math.Abs(endOriginalRedline.Offset - endTime) > Precision.DOUBLE_EPSILON) { var copy = endOriginalRedline.Copy(); copy.Offset = endTime; newTiming.Add(copy); } } lastEndTime = endTime; } // Transform the beat time back to millisecond time Timing transformNewTiming = newTiming; if (scaleToNewTiming) { // Transform back the timing transformNewTiming = newTiming.Copy(); foreach (var tp in transformNewTiming.TimingPoints) { tp.Offset = Math.Floor(newTiming.GetMilliseconds(tp.Offset, patternStartTime) + Precision.DOUBLE_EPSILON); } // Transform back the parts foreach (Part part in parts) { part.StartTime = Math.Floor(newTiming.GetMilliseconds(part.StartTime, patternStartTime)); part.EndTime = Math.Floor(newTiming.GetMilliseconds(part.EndTime, patternStartTime)); } // Transform everything to millisecond time relative to pattern start time foreach (var ho in patternBeatmap.HitObjects) { // Calculate the millisecond end time before changing the start time because the end time getter uses the beat time start time var msEndTime = newTiming.GetMilliseconds(ho.GetEndTime(false), patternStartTime); ho.Time = newTiming.GetMilliseconds(ho.Time, patternStartTime); // End time has to be set after the time because the end time setter uses the millisecond start time ho.EndTime = msEndTime; foreach (var tp in ho.BodyHitsounds) { tp.Offset = newTiming.GetMilliseconds(tp.Offset, patternStartTime); } // It is necessary to resnap early so it can recalculate the duration using the right offset if (SnapToNewTiming) { ho.ResnapSelf(transformNewTiming, BeatDivisors); } if (ho.IsSlider) { ho.CalculateSliderTemporalLength(transformNewTiming, true); } ho.UnInheritedTimingPoint = transformNewTiming.GetRedlineAtTime(ho.Time); ho.UpdateTimelineObjectTimes(); } foreach (var tp in kiaiToggles.Concat(svChanges)) { tp.Offset = Math.Floor(newTiming.GetMilliseconds(tp.Offset, patternStartTime)); } } // Apply custom scale and rotate if (Math.Abs(spatialScale - 1) > Precision.DOUBLE_EPSILON || Math.Abs(CustomRotate) > Precision.DOUBLE_EPSILON) { // Create a transformation matrix for the custom scale and rotate // The rotation is inverted because the default osu! rotation goes clockwise Matrix2 transform = Matrix2.Mult(Matrix2.CreateScale(spatialScale), Matrix2.CreateRotation(-CustomRotate)); Vector2 centre = new Vector2(256, 192); foreach (var ho in patternBeatmap.HitObjects) { ho.Move(-centre); ho.Transform(transform); ho.Move(centre); // Scale pixel length and SV for sliders aswell if (ho.IsSlider) { ho.PixelLength *= spatialScale; ho.SliderVelocity /= spatialScale; } } // osu! clips coordinates to the bounds (0,512), so there is some space downwards to still place the pattern // Calculate the new bounds of the pattern and try to place it in the playfield var minX = patternBeatmap.HitObjects.Min(o => o.Pos.X); var minY = patternBeatmap.HitObjects.Min(o => o.Pos.Y); Vector2 offset = new Vector2(Math.Max(-minX, 0), Math.Max(-minY, 0)); if (offset.LengthSquared > 0) { foreach (var ho in patternBeatmap.HitObjects) { ho.Move(offset); } } } // Manualify stacks if (FixStackLeniency) { // If scale to new timing was used update the circle size of the pattern, // so it calculates stacks at the new size of the pattern. if (ScaleToNewCircleSize) { patternBeatmap.Difficulty["CircleSize"].DoubleValue = originalCircleSize; } patternBeatmap.CalculateEndPositions(); patternBeatmap.UpdateStacking(rounded: true); // Manualify by setting the base position to the stacked position foreach (var ho in patternBeatmap.HitObjects) { var offset = ho.StackedPos - ho.Pos; ho.Move(offset); } } // Resnap everything to the new timing. if (SnapToNewTiming) { // Resnap all objects foreach (HitObject ho in patternBeatmap.HitObjects) { ho.ResnapSelf(transformNewTiming, BeatDivisors); ho.ResnapEnd(transformNewTiming, BeatDivisors); ho.ResnapPosition(targetMode, patternCircleSize); // Resnap to column X positions for mania only } // Resnap Kiai toggles foreach (TimingPoint tp in kiaiToggles) { tp.ResnapSelf(transformNewTiming, BeatDivisors); } // Resnap SliderVelocity changes foreach (TimingPoint tp in svChanges) { tp.ResnapSelf(transformNewTiming, BeatDivisors); } } // Multiply BPM and divide SV foreach (var part in parts) { foreach (var tp in transformNewTiming.GetRedlinesInRange(part.StartTime - 2 * Precision.DOUBLE_EPSILON, part.EndTime, false)) { tp.MpB /= bpmMultiplier; // MpB is the inverse of the BPM } foreach (var ho in part.HitObjects) { ho.SliderVelocity *= bpmMultiplier; // SliderVelocity is the inverse of the multiplier } } // Make new timingpoints changes for the hitsounds and other stuff // Add redlines timingPointsChanges = transformNewTiming.Redlines.Select(tp => new TimingPointsChange(tp, mpb: true, meter: true, unInherited: true, omitFirstBarLine: true, fuzzyness: Precision.DOUBLE_EPSILON)).ToList(); // Add SliderVelocity changes for taiko and mania if (includePatternSliderVelocity && (targetMode == GameMode.Taiko || targetMode == GameMode.Mania)) { timingPointsChanges.AddRange(svChanges.Select(tp => new TimingPointsChange(tp, mpb: true, fuzzyness: 0.4))); } // Add Kiai toggles timingPointsChanges.AddRange(kiaiToggles.Select(tp => new TimingPointsChange(tp, kiai: true))); // Add Hitobject stuff foreach (HitObject ho in patternBeatmap.HitObjects) { if (ho.IsSlider) // SliderVelocity changes { TimingPoint tp = ho.TimingPoint.Copy(); tp.Offset = ho.Time; tp.MpB = ho.SliderVelocity; timingPointsChanges.Add(new TimingPointsChange(tp, mpb: true, fuzzyness: 0.4)); } if (!IncludeHitsounds) { // Remove hitsounds and skip adding body hitsounds ho.ResetHitsounds(); continue; } if (includeTimingPointHitsounds) { // Body hitsounds bool vol = ho.IsSlider || ho.IsSpinner; bool sam = ho.IsSlider && ho.SampleSet == 0; bool ind = ho.IsSlider; timingPointsChanges.AddRange(ho.BodyHitsounds.Select(tp => new TimingPointsChange(tp, volume: vol, index: ind, sampleset: sam))); } } // Add timeline hitsounds if (includeTimingPointHitsounds) { foreach (TimelineObject tlo in patternTimeline.TimelineObjects) { if (tlo.HasHitsound) { // Add greenlines for hitsounds TimingPoint tp = tlo.HitsoundTimingPoint.Copy(); tp.Offset = tlo.Time; timingPointsChanges.Add(new TimingPointsChange(tp, sampleset: true, volume: true, index: true)); } } } // Replace the old timingpoints patternTiming.Clear(); TimingPointsChange.ApplyChanges(patternTiming, timingPointsChanges); patternBeatmap.GiveObjectsGreenlines(); patternBeatmap.CalculateSliderEndTimes(); }
void UpdateTiming() { // find section index int newIndex = sectionIndex_; int oldSample = currentSample_; currentSample_ = musicSource_.timeSamples; if (sectionIndex_ + 1 >= Sections.Count) { if (currentSample_ < oldSample) { newIndex = 0; } } else { if (Sections[sectionIndex_ + 1].StartTimeSamples <= currentSample_) { newIndex = sectionIndex_ + 1; } } if (newIndex != sectionIndex_) { sectionIndex_ = newIndex; OnSectionChanged(); } // calc current timing isNearChanged_ = false; isJustChanged_ = false; int sectionSample = currentSample_ - CurrentSection_.StartTimeSamples; if (sectionSample >= 0) { just_.Bar = (int)(sectionSample / samplesPerBar_) + CurrentSection_.StartTiming.Bar; just_.Beat = (int)((sectionSample % samplesPerBar_) / samplesPerBeat_) + CurrentSection_.StartTiming.Beat; just_.Unit = (int)(((sectionSample % samplesPerBar_) % samplesPerBeat_) / samplesPerUnit_) + CurrentSection_.StartTiming.Unit; just_.Fix(CurrentSection_); if (sectionIndex_ + 1 >= Sections.Count) { if (numLoopBar_ > 0) { while (just_.Bar >= numLoopBar_) { just_.Decrement(CurrentSection_); } } } else { while (just_ >= Sections[sectionIndex_ + 1].StartTiming) { just_.Decrement(CurrentSection_); } } just_.Subtract(CurrentSection_.StartTiming, CurrentSection_); timeSecFromJust_ = (double)(sectionSample - just_.Bar * samplesPerBar_ - just_.Beat * samplesPerBeat_ - just_.Unit * samplesPerUnit_) / (double)samplingRate_; isFormerHalf_ = (timeSecFromJust_ * samplingRate_) < samplesPerUnit_ / 2; just_.Add(CurrentSection_.StartTiming, CurrentSection_); near_.Copy(just_); if (!isFormerHalf_) { near_.Increment(CurrentSection_); } if (samplesInLoop_ != 0 && currentSample_ + samplesPerUnit_ / 2 >= samplesInLoop_) { near_.Init(); } isNearChanged_ = (near_.Equals(oldNear_) == false); isJustChanged_ = (just_.Equals(oldJust_) == false); CallEvents(); oldNear_.Copy(near_); oldJust_.Copy(just_); } if (DebugText != null) { DebugText.text = "Just = " + Just.ToString() + ", MusicalTime = " + MusicalTime_; if (Sections.Count > 0) { DebugText.text += System.Environment.NewLine + "section[" + sectionIndex_ + "] = " + CurrentSection_.ToString(); } } }
void UpdateTiming() { isNowChanged_ = false; isJustChanged_ = false; if (SectionIndex < 0 || sections.Count <= SectionIndex) { Debug.LogWarning("Music:" + name + " has invalid SectionIndex = " + SectionIndex + ", sections.Count = " + sections.Count); return; } long numSamples = MusicSource.GetTimeSamples(); int NewIndex = -1; for (int i = SectionIndex; i < sections.Count; i++) { if (sections[i].StartTimeSamples_ <= numSamples && (sections.Count <= i + 1 || numSamples < sections[i + 1].StartTimeSamples_)) { NewIndex = i; break; } } if (NewIndex < 0) { if (0 <= numSamples && numSamples < delayTimeSamples) { NewIndex = 0; Initialize(); OnSectionChanged(); } else { for (int i = 0; i < SectionIndex; i++) { if (sections[i].StartTimeSamples_ <= numSamples && numSamples < sections[i + 1].StartTimeSamples_) { NewIndex = i; } } } } if (NewIndex != SectionIndex) { SectionIndex = NewIndex; OnSectionChanged(); } numSamples -= CurrentSection_.StartTimeSamples_; if (numSamples >= 0) { Just_.bar = (int)(numSamples / SamplesPerBar) + CurrentSection_.StartTiming_.bar; Just_.beat = (int)((numSamples % SamplesPerBar) / SamplesPerBeat) + CurrentSection_.StartTiming_.beat; Just_.unit = (int)(((numSamples % SamplesPerBar) % SamplesPerBeat) / SamplesPerUnit) + CurrentSection_.StartTiming_.unit; if (Just_.unit >= CurrentSection_.mtBeat_) { Just_.beat += (int)(Just_.unit / CurrentSection_.mtBeat_); Just_.unit %= CurrentSection_.mtBeat_; } int barUnit = Just_.beat * CurrentSection_.mtBeat_ + Just_.unit; if (barUnit >= CurrentSection_.mtBar_) { Just_.bar += (int)(barUnit / CurrentSection_.mtBar_); Just_.beat = 0; Just_.unit = (barUnit % CurrentSection_.mtBar_); if (Just_.unit >= CurrentSection_.mtBeat_) { Just_.beat += (int)(Just_.unit / CurrentSection_.mtBeat_); Just_.unit %= CurrentSection_.mtBeat_; } } if (NumLoopBar > 0) { Just_.bar %= NumLoopBar; } isFormerHalf_ = (numSamples % SamplesPerUnit) < SamplesPerUnit / 2; dtFromJust_ = (double)(numSamples % SamplesPerUnit) / (double)SamplingRate; Now_.Copy(Just_); if (!isFormerHalf_) { Now_.Increment(); } if (SamplesInLoop != 0 && numSamples + SamplesPerUnit / 2 >= SamplesInLoop) { Now_.Init(); } isNowChanged_ = Now_.totalUnit != OldNow.totalUnit; isJustChanged_ = Just_.totalUnit != OldJust.totalUnit; CallEvents(); OldNow.Copy(Now_); OldJust.Copy(Just_); } else { //Debug.LogWarning( "Warning!! Failed to GetNumPlayedSamples" ); } DebugUpdateText(); }