// volumeEnvelope = series of time/volume pairs. static public void AdjustVolume(short[] wave, List <SampleVolumePair> volumeEnvelope) { // Enforce the first/last times to cover the entire range. Debug.Assert(volumeEnvelope[0].sample == 0); Debug.Assert(volumeEnvelope[volumeEnvelope.Count - 1].sample == wave.Length - 1); // Simple smoothstep interpolation (which is equivalent to cosine interpolation). for (int i = 0; i < volumeEnvelope.Count - 1; i++) { var s0 = volumeEnvelope[i + 0].sample; var s1 = volumeEnvelope[i + 1].sample; var v0 = volumeEnvelope[i + 0].volume; var v1 = volumeEnvelope[i + 1].volume; for (int j = s0; j <= s1; j++) { var ratio = s0 == s1 ? 0.0f : (j - s0) / (float)(s1 - s0); var volume = Utils.Lerp(v0, v1, Utils.SmoothStep(ratio)); wave[j] = (short)Utils.Clamp((int)Math.Round(wave[j] * volume), short.MinValue, short.MaxValue); } } }
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.SmoothStep(lerp)); } } } } }