/**<summary>Redraws the control.</summary>*/ protected override void OnRender(DrawingContext d) { // Set guidelines for pixel perfection GuidelineSet guidelines = new GuidelineSet(); guidelines.GuidelinesX.Add(0.5); guidelines.GuidelinesX.Add(ActualHeight + 0.5); guidelines.GuidelinesY.Add(0.5); guidelines.GuidelinesY.Add(ActualWidth + 0.5); d.PushGuidelineSet(guidelines); // Highlight the midi's octave range if (!DesignerProperties.GetIsInDesignMode(this) && IsTrackLoaded) { int min = (AllTracksLoaded ? 0 : trackIndex); int max = (AllTracksLoaded ? Config.Midi.TrackCount : trackIndex + 1); for (int index = min; index < max; index++) { if (!Config.Midi.GetTrackSettingsAt(index).Enabled&& AllTracksLoaded) { continue; } double high = GetSemitoneY((Config.Midi.GetTrackSettingsAt(index).OctaveOffset + 3) * 12); double low = GetSemitoneY((Config.Midi.GetTrackSettingsAt(index).OctaveOffset + 1) * 12); d.DrawRectangle(Brushes.LightYellow, null, Floor(new Rect( GetProgressX(0), high, ActualWidthSpacing, low - high ))); } for (int index = min; index < max; index++) { if (!Config.Midi.GetTrackSettingsAt(index).Enabled&& AllTracksLoaded) { continue; } double high = GetSemitoneY((Config.Midi.GetTrackSettingsAt(index).OctaveOffset + 3) * 12); double low = GetSemitoneY((Config.Midi.GetTrackSettingsAt(index).OctaveOffset + 1) * 12); d.DrawRectangle(null, new Pen(Brushes.Gold, 3), Floor(new Rect( GetProgressX(0), high, ActualWidthSpacing, low - high ))); } } // Draw the octave lines and labels for (int i = 0; i <= OctaveRange; i++) { double y = TrackGraph.SpacingY + ActualHeightSpacing / OctaveRange * i; d.DrawLine(new Pen(Brushes.LightGray, 1), Floor(new Point(SpacingLeft, y)), Floor(new Point(ActualWidth - SpacingRight, y))); var formattedText = new FormattedText( "C" + (highestOctave - i), CultureInfo.GetCultureInfo("en-us"), FlowDirection.LeftToRight, new Typeface("Segoe UI"), 12, Brushes.Black ); y -= formattedText.Height / 2; d.DrawText(formattedText, Floor(new Point(SpacingLabel, y))); } // Stop using the guidelines d.Pop(); // The milliseconds of the next playable note int nextPlayable = 0; // The use time in milliseconds (Checked if not in designer) int useTime = 0; // Current milliseconds of the midi playback int currentMilliseconds = 0; if (!DesignerProperties.GetIsInDesignMode(this)) { useTime = Config.UseTime * 1000 / 60; currentMilliseconds = Config.Sequencer.CurrentTime; } // Draw all notes for (int i = 0; i < notes.Count; i++) { TrackNote note = notes[i]; note.Semitone += Config.Midi.NoteOffset; if (note.Semitone >= 0 && note.Semitone <= 132) { double x = GetProgressX(note.Progress); double y = GetSemitoneY(note.Semitone); bool skipped = note.Milliseconds < nextPlayable; bool inRange = IsNoteInsideRange(note); bool recentlyPlayed = note.Milliseconds <= currentMilliseconds && note.Milliseconds + RecentlyPlayedThreshold >= currentMilliseconds && Config.Midi.GetTrackSettingsAt(note.TrackIndex).Enabled; SolidColorBrush brush; if (skipped) { brush = SkippedNoteBrush; if (!ShowSkippedNotes) { continue; } } else { nextPlayable = note.Milliseconds + useTime + 2; if (!inRange) { brush = WrappedNoteBrush; if (!ShowWrappedNotes) { continue; } } else { brush = ValidNoteBrush; if (!ShowValidNotes) { continue; } } } if (recentlyPlayed && drawingPlayback && (!skipped || !Config.SkipPianoMode)) { double scale = (double)(currentMilliseconds - note.Milliseconds) / RecentlyPlayedThreshold; brush = Lerp(RecentlyPlayedNoteBrush, brush, scale); } // Don't floor the ellipse d.DrawEllipse(brush, null, new Point(x, y), 2, 2); } } }
/**<summary>Gets if the note is within the midi's octave range.</summary>*/ private bool IsNoteInsideRange(TrackNote note) { return(note.Semitone - (Config.Midi.GetTrackSettingsAt(note.TrackIndex).OctaveOffset + 1) * 12 >= 0 && note.Semitone - (Config.Midi.GetTrackSettingsAt(note.TrackIndex).OctaveOffset + 1) * 12 <= 24); }
/**<summary>Updates changes made to the track.</summary>*/ public void Update() { lowestOctave = 10; highestOctave = -1; int min = (AllTracksLoaded ? 0 : trackIndex); int max = (AllTracksLoaded ? Config.Midi.TrackCount : trackIndex + 1); for (int index = min; index < max; index++) { if (!Config.Midi.GetTrackSettingsAt(index).Enabled&& AllTracksLoaded) { continue; } lowestOctave = Math.Min(lowestOctave, Math.Max(-1, Math.Min( Config.Midi.GetTrackSettingsAt(index).OctaveOffset - 1, (Config.Midi.GetTrackAt(index).LowestNote + Config.Midi.NoteOffset) / 12 - 1 ))); highestOctave = Math.Max(highestOctave, Math.Min(10, Math.Max( Config.Midi.GetTrackSettingsAt(index).OctaveOffset + 3, (Config.Midi.GetTrackAt(index).HighestNote + Config.Midi.NoteOffset - 1) / 12 ))); } if (lowestOctave >= highestOctave) { lowestOctave = -1; highestOctave = 10; } // The milliseconds of the next playable note int nextPlayable = 0; // The use time in milliseconds (Checked if not in designer) int useTime = 0; ValidNotes = 0; WrappedNotes = 0; SkippedNotes = 0; // Count note categories for (int i = 0; i < notes.Count; i++) { TrackNote note = notes[i]; note.Semitone += Config.Midi.NoteOffset; if (note.Semitone >= 0 && note.Semitone <= 132) { bool skipped = note.Milliseconds < nextPlayable; bool inRange = IsNoteInsideRange(note); if (skipped) { SkippedNotes++; } else { nextPlayable = note.Milliseconds + useTime + 2; if (!inRange) { WrappedNotes++; } else { ValidNotes++; } } } else { SkippedNotes++; } } InvalidateVisual(); }