private Rectangle GetExpandedListRect() { var rect = popupRect; if (IsLandscape) { rect.X = -(int)Math.Round(rect.Width * Utils.SmootherStep(popupRatio)); } else { rect.Y = -(int)Math.Round(rect.Height * Utils.SmootherStep(popupRatio)); } return(rect); }
private void ComputeChannelsScroll(VideoFrameMetadata[] frames, int channelMask, int numVisibleNotes) { var numFrames = frames.Length; var numChannels = frames[0].channelNotes.Length; for (int c = 0; c < numChannels; c++) { if ((channelMask & (1 << c)) == 0) { continue; } // Go through all the frames and split them in segments. // A segment is a section of the song where all the notes fit in the view. var segments = new List <ScrollSegment>(); var currentSegment = (ScrollSegment)null; var minOverallNote = int.MaxValue; var maxOverallNote = int.MinValue; for (int f = 0; f < numFrames; f++) { var frame = frames[f]; var note = frame.channelNotes[c]; if (frame.scroll == null) { frame.scroll = new float[numChannels]; } if (note.IsMusical) { if (currentSegment == null) { currentSegment = new ScrollSegment(); segments.Add(currentSegment); } // If its the start of a new pattern and we've been not moving for ~10 sec, let's start a new segment. bool forceNewSegment = frame.playNote == 0 && (f - currentSegment.startFrame) > 600; var minNoteValue = note.Value - 1; var maxNoteValue = note.Value + 1; // Only consider slides if they arent too large. if (note.IsSlideNote && Math.Abs(note.SlideNoteTarget - note.Value) < numVisibleNotes / 2) { minNoteValue = Math.Min(note.Value, note.SlideNoteTarget) - 1; maxNoteValue = Math.Max(note.Value, note.SlideNoteTarget) + 1; } // Only consider arpeggios if they are not too big. if (note.IsArpeggio && note.Arpeggio.GetChordMinMaxOffset(out var minArp, out var maxArp) && maxArp - minArp < numVisibleNotes / 2) { minNoteValue = note.Value + minArp; maxNoteValue = note.Value + maxArp; } minOverallNote = Math.Min(minOverallNote, minNoteValue); maxOverallNote = Math.Max(maxOverallNote, maxNoteValue); var newMinNote = Math.Min(currentSegment.minNote, minNoteValue); var newMaxNote = Math.Max(currentSegment.maxNote, maxNoteValue); // If we cant fit the next note in the view, start a new segment. if (forceNewSegment || newMaxNote - newMinNote + 1 > numVisibleNotes) { currentSegment.endFrame = f; currentSegment = new ScrollSegment(); currentSegment.startFrame = f; segments.Add(currentSegment); currentSegment.minNote = minNoteValue; currentSegment.maxNote = maxNoteValue; } else { currentSegment.minNote = newMinNote; currentSegment.maxNote = newMaxNote; } } } // Not a single notes in this channel... if (currentSegment == null) { currentSegment = new ScrollSegment(); currentSegment.minNote = Note.FromFriendlyName("C4"); currentSegment.maxNote = currentSegment.minNote; segments.Add(currentSegment); } currentSegment.endFrame = numFrames; // Remove very small segments, these make the camera move too fast, looks bad. var shortestAllowedSegment = SegmentTransitionNumFrames * 2; bool removed = false; do { var sortedSegment = new List <ScrollSegment>(segments); sortedSegment.Sort((s1, s2) => s1.NumFrames.CompareTo(s2.NumFrames)); if (sortedSegment[0].NumFrames >= shortestAllowedSegment) { break; } for (int s = 0; s < sortedSegment.Count; s++) { var seg = sortedSegment[s]; if (seg.NumFrames >= shortestAllowedSegment) { break; } var thisSegmentIndex = segments.IndexOf(seg); // Segment is too short, see if we can merge with previous/next one. var mergeSegmentIndex = -1; var mergeSegmentLength = -1; if (thisSegmentIndex > 0) { mergeSegmentIndex = thisSegmentIndex - 1; mergeSegmentLength = segments[thisSegmentIndex - 1].NumFrames; } if (thisSegmentIndex != segments.Count - 1 && segments[thisSegmentIndex + 1].NumFrames > mergeSegmentLength) { mergeSegmentIndex = thisSegmentIndex + 1; mergeSegmentLength = segments[thisSegmentIndex + 1].NumFrames; } if (mergeSegmentIndex >= 0) { // Merge. var mergeSeg = segments[mergeSegmentIndex]; mergeSeg.startFrame = Math.Min(mergeSeg.startFrame, seg.startFrame); mergeSeg.endFrame = Math.Max(mergeSeg.endFrame, seg.endFrame); segments.RemoveAt(thisSegmentIndex); removed = true; break; } } }while (removed); // Build the actually scrolling data. var minScroll = (float)Math.Ceiling(Note.MusicalNoteMin + numVisibleNotes * 0.5f); var maxScroll = (float)Math.Floor(Note.MusicalNoteMax - numVisibleNotes * 0.5f); Debug.Assert(maxScroll >= minScroll); foreach (var segment in segments) { segment.scroll = Utils.Clamp(segment.minNote + (segment.maxNote - segment.minNote) * 0.5f, minScroll, maxScroll); } for (var s = 0; s < segments.Count; s++) { var segment0 = segments[s + 0]; var segment1 = s == segments.Count - 1 ? null : segments[s + 1]; for (int f = segment0.startFrame; f < segment0.endFrame - (segment1 == null ? 0 : SegmentTransitionNumFrames); f++) { frames[f].scroll[c] = segment0.scroll; } if (segment1 != null) { // Smooth transition to next segment. for (int f = segment0.endFrame - SegmentTransitionNumFrames, a = 0; f < segment0.endFrame; f++, a++) { var lerp = a / (float)SegmentTransitionNumFrames; frames[f].scroll[c] = Utils.Lerp(segment0.scroll, segment1.scroll, Utils.SmootherStep(lerp)); } } } } }