123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758 |
- using SongVocalSectionAnalyser.AudioAnalysis;
- using SongVocalSectionAnalyser.Common;
- using SongVocalSectionAnalyser.Common.Collections;
- using System;
- using System.Collections.Generic;
- using System.Drawing;
- using System.Drawing.Imaging;
- using System.Linq;
- using System.Windows.Forms;
- namespace SongVocalSectionAnalyser.UI
- {
- public partial class InteractiveChart : UserControl
- {
- private const int L0Background = 0;
- private const int L1Samples = 1;
- private const int L2ChunkBars = 2;
- private const int L3MaCurve = 3;
- private const int L4Fg = 4;
- private const int L5ActiveOverlay = 5;
- private const int MaxLayers = 6;
- private readonly StyleSchemeImpl _styleScheme;
- private SongData _songData;
- private bool _flagIgnorePaintRect;
- private readonly bool[] _flagUpdates;
- private readonly Bitmap[] _bmLayers;
- private readonly Action[] _renderActions;
- private float _maxMa;
- private float _maxChunk;
- private ChunkGroup[] _chunkGroups;
- private ChunkGroup _currentGroup;
- private int? _playingSample;
- private int ChartFrameWidth => Math.Max(1, ChartPictureBox.ClientSize.Width - _styleScheme.ChartFramePadding * 2);
- private int UnitsInChartFrame => (int)Math.Floor(ChartFrameWidth / ScaleByUpDown.Value);
- private Rectangle ChartFrameRectangle => new Rectangle(_styleScheme.ChartFramePadding, _styleScheme.ChartFramePadding, ChartFrameWidth, ChartPictureBox.ClientSize.Height - _styleScheme.ChartFramePadding * 2);
- public event Action<TimeSpan, ChunkGroup> TimelineClicked;
- public InteractiveChartStyleScheme Style => _styleScheme;
- public int? PlayingSample
- {
- get => _playingSample;
- set
- {
- if (null == _playingSample && null == value) return;
- if (_playingSample == value) return;
- if (null == _playingSample && value.HasValue || _playingSample.HasValue && null == value)
- {
- _playingSample = value;
- _flagUpdates[L5ActiveOverlay] = true;
- ChartPictureBox.Invalidate();
- return;
- }
- if (SampleRadioButton.Checked)
- {
- //todo detect playing sample change scale by sample
- }
- else
- {
- var pixelPerSample = 1m / _songData.SamplePerChunk * ScaleByUpDown.Value;
- var oldPixel = (int)(_playingSample * pixelPerSample);
- var newPixel = (int)(value.Value * pixelPerSample);
- _playingSample = value;
- if (oldPixel != newPixel)
- {
- _flagUpdates[L5ActiveOverlay] = true;
- ChartPictureBox.Invalidate();
- }
- }
- }
- }
- public InteractiveChart()
- {
- _styleScheme = new StyleSchemeImpl();
- InitializeComponent();
- _flagUpdates = new bool[MaxLayers];
- _bmLayers = new Bitmap[MaxLayers];
- _renderActions = new Action[]
- {
- RenderL0Background,
- RenderL1Samples,
- RenderL2Chunks,
- RenderL3MaCurve,
- RenderL4Fg,
- RenderL5ActiveOverlay,
- };
- }
- public void SetData(SongData data)
- {
- _songData = data;
- if (null == _songData)
- {
- _chunkGroups = Array.Empty<ChunkGroup>();
- _currentGroup = null;
- return;
- }
- _maxChunk = _songData.Chunks.Select(p => p.Value).Max();
- _maxMa = _songData.MaLine.Max();
- _chunkGroups = _songData.Chunks.GroupContiguous(p => p.OverThreshold)
- .Select(p => new ChunkGroup(p.Elements.Min(q => q.Begin), p.Elements.Max(q => q.End))).ToArray();
- UpdateChartScale();
- }
- public TimeSpan GetCurrentPosition()
- {
- throw new NotImplementedException();
- }
- public void SetCurrentPosition(TimeSpan position)
- {
- //if sliding window position changed
- // set bg/fg flag both
- //if position changed only
- // just set fg flag
- throw new NotImplementedException();
- }
- protected virtual void OnSampleClicked(TimeSpan time)
- {
- TimelineClicked?.Invoke(time, _currentGroup);
- }
- private void UpdateChartScale()
- {
- if (null == _songData)
- {
- //reset
- }
- else
- {
- if (SampleRadioButton.Checked)
- {
- BottomScrollBar.Maximum = _songData.Samples.Count - 1;
- }
- else
- {
- BottomScrollBar.Maximum = _songData.Chunks.Count - 1;
- }
- BottomScrollBar.LargeChange = UnitsInChartFrame;
- }
- //update all layer
- ReDrawAll();
- }
- private void ReDrawAll()
- {
- for (var i = 0; i < MaxLayers; i++) _flagUpdates[i] = true;
- _flagIgnorePaintRect = true;
- ChartPictureBox.Invalidate();
- }
- private void RenderL0Background()
- {
- var framePen = _styleScheme.FramePen;
- var rcFrame = ChartFrameRectangle;
- using (var dc = Graphics.FromImage(_bmLayers[L0Background]))
- {
- dc.SetHighQuility();
- dc.Clear(Color.White);
- //draw frame
- dc.DrawRectangle(framePen, rcFrame);
- //TODO: draw axis and text
- // V:sample value
- // H:timeline
- // HS:time unit
- }
- }
- private void RenderL1Samples()
- {
- if (null == _songData) return;
- var rcFrame = ChartFrameRectangle;
- var frameHeightHalf = rcFrame.Height / 2;
- var samplePen = _styleScheme.SamplePen;
- //TODO: fast render samples
- // <2 Per pixel : single points
- // 2+ Per pixel : extract max/min value draw vertical line
- using (var dc = Graphics.FromImage(_bmLayers[L1Samples]))
- {
- dc.SetHighQuility();
- dc.Clear(Color.Transparent);
- var vmY = rcFrame.Top + frameHeightHalf;
- if (SampleRadioButton.Checked) //draw scale by sample
- {
- //draw sample
- var sampleIndex = BottomScrollBar.Value;
- var pixelRatio = (float)ScaleByUpDown.Value;
- var samplePoints = new List<PointF>(ChartFrameWidth);
- for (var x = 0; x < UnitsInChartFrame && sampleIndex < _songData.Samples.Count; x++, sampleIndex++)
- {
- var sampleData = _songData.Samples[sampleIndex];
- samplePoints.Add(
- new PointF(
- rcFrame.Left + x * pixelRatio,
- vmY + frameHeightHalf * (sampleData / 32768f)
- )
- );
- }
- if (1 < samplePoints.Count) dc.DrawLines(samplePen, samplePoints.ToArray());
- }
- else //draw scale by chunks
- {
- //draw samples scale by chunk
- {
- float sampleIndex = BottomScrollBar.Value * _songData.SamplePerChunk;
- var samplesInFrame = UnitsInChartFrame * _songData.SamplePerChunk;
- var pixelRatio = rcFrame.Width / (float)samplesInFrame;
- var samplePoints = new List<PointF>(ChartFrameWidth);
- var step = (float)samplesInFrame / rcFrame.Width / 32;
- for (var x = 0f;
- x < samplesInFrame && sampleIndex < _songData.Samples.Count;
- x += step, sampleIndex += step)
- {
- var sampleData = _songData.Samples[(int)sampleIndex];
- samplePoints.Add(
- new PointF(
- rcFrame.Left + x * pixelRatio,
- vmY + frameHeightHalf * (sampleData / 32768f)
- )
- );
- }
- if (1 < samplePoints.Count) dc.DrawLines(samplePen, samplePoints.ToArray());
- }
- }
- }
- }
- private void RenderL2Chunks()
- {
- if (null == _songData) return;
- const int jump = 15;
- var rcFrame = ChartFrameRectangle;
- var frameHeightHalf = rcFrame.Height / 2;
- var chunkBrush = _styleScheme.ChunkBrush;
- var chunkThresholdBrush = _styleScheme.ChunkThresholdBrush;
- using (var dc = Graphics.FromImage(_bmLayers[L2ChunkBars]))
- {
- dc.SetHighQuility();
- dc.Clear(Color.Transparent);
- if (SampleRadioButton.Checked) //draw scale by sample
- {
- //TODO: draw chunks bars scale by sample
- }
- else
- {
- //draw chunk bars
- var chunkIndex = BottomScrollBar.Value;
- var pixelRatio = (float)ScaleByUpDown.Value;
- for (var x = 0; x < UnitsInChartFrame && chunkIndex < _songData.Chunks.Count; x++, chunkIndex++)
- {
- var chunkData = _songData.Chunks[chunkIndex];
- var pt = new PointF(
- rcFrame.Left + x * pixelRatio,
- rcFrame.Bottom - frameHeightHalf * (chunkData.Value / _maxChunk));
- var barBottom = rcFrame.Bottom;
- var sampleTime = GetPointSampleTime(new Point((int)pt.X, (int)pt.Y));
- if (true == _currentGroup?.Contains(sampleTime))
- {
- barBottom += jump;
- pt.Y += jump;
- }
- dc.FillRectangle(chunkData.OverThreshold ? chunkThresholdBrush : chunkBrush
- , new RectangleF(pt.X, pt.Y, pixelRatio, barBottom - pt.Y));
- if (chunkData.Debounced)
- dc.FillRectangle(chunkData.OverThreshold ? chunkThresholdBrush : chunkBrush
- , new RectangleF(pt.X, pt.Y, pixelRatio, barBottom - pt.Y));
- }
- }
- }
- }
- private void RenderL3MaCurve()
- {
- if (null == _songData) return;
- var rcFrame = ChartFrameRectangle;
- var frameHeightHalf = rcFrame.Height / 2;
- using (var dc = Graphics.FromImage(_bmLayers[L3MaCurve]))
- {
- dc.SetHighQuility();
- dc.Clear(Color.Transparent);
- if (SampleRadioButton.Checked) //draw scale by sample
- {
- //TODO: draw ma curve scale by sample
- }
- else
- {
- //draw MA curve
- var maIndex = BottomScrollBar.Value;
- var samplePixelRatio = (float)ScaleByUpDown.Value;
- var maPoints = new List<PointF>(ChartFrameWidth);
- for (var x = 0; x < UnitsInChartFrame && maIndex < _songData.MaLine.Count; x++, maIndex++)
- {
- var maData = _songData.MaLine[maIndex];
- maPoints.Add(
- new PointF(rcFrame.Left + x * samplePixelRatio
- , rcFrame.Bottom - frameHeightHalf * (float)(maData / _maxMa))
- );
- }
- if (1 < maPoints.Count) dc.DrawLines(_styleScheme.MaCurvePen, maPoints.ToArray());
- }
- }
- }
- private void RenderL4Fg()
- {
- var framePen = _styleScheme.FramePen;
- var rcFrame = ChartFrameRectangle;
- var frameHeightHalf = rcFrame.Height / 2;
- var vmY = rcFrame.Top + frameHeightHalf;
- using (var dc = Graphics.FromImage(_bmLayers[L4Fg]))
- {
- // VM:sample value 0
- dc.DrawLine(framePen, rcFrame.Left, vmY, rcFrame.Right, vmY);
- }
- }
- private void RenderL5ActiveOverlay()
- {
-
- var frameRc = ChartFrameRectangle;
- var pointToClient = ChartPictureBox.PointToClient(MousePosition);
- using (var dc = Graphics.FromImage(_bmLayers[L5ActiveOverlay]))
- {
- dc.SetHighQuility();
- dc.Clear(Color.Transparent);
- if (null != _songData)
- {
- if (frameRc.Contains(pointToClient))
- {
- var time = GetPointSampleTime(pointToClient);
- dc.DrawString(time.ToStringH2M2S2F2(), Font, _styleScheme.FrameBrush, 0, 0);
- if (null != _currentGroup)
- {
- var chunkGroupIndex = Array.IndexOf(_chunkGroups, _currentGroup);
- var chunkInfo = $"#{chunkGroupIndex} {(_currentGroup.End - _currentGroup.Begin).ToStringH2M2S2F2()} [ {_currentGroup.Begin.ToStringH2M2S2F2()} -> {_currentGroup.End.ToStringH2M2S2F2()} ]";
- dc.DrawString(chunkInfo, Font, _styleScheme.FrameBrush, new RectangleF(PointF.Empty, ChartPictureBox.ClientSize), new StringFormat() { Alignment = StringAlignment.Far });
- }
- //mouse vertical bar
- dc.DrawLine(_styleScheme.CursorPen,
- new Point(pointToClient.X, frameRc.Top),
- new Point(pointToClient.X, frameRc.Bottom)
- );
- }
- //playing bar
- if (_playingSample.HasValue)
- {
- if (SampleRadioButton.Checked)
- {
- //TODO: draw playing bar scale by sample
- }
- else
- {
- var windowLeftSample = BottomScrollBar.Value * _songData.SamplePerChunk;
- var windowRightSample = windowLeftSample + UnitsInChartFrame * _songData.SamplePerChunk;
- if (windowLeftSample <= _playingSample && _playingSample < windowRightSample)
- {
- var pixelPerSample = 1m / _songData.SamplePerChunk * ScaleByUpDown.Value;
- var x = (int)(frameRc.X + (_playingSample - windowLeftSample) * pixelPerSample);
- dc.DrawLine(_styleScheme.PlayingPen,
- new Point(x, frameRc.Top),
- new Point(x, frameRc.Bottom)
- );
- }
- }
- }
- }
- }
- }
- private TimeSpan GetPointSampleTime(Point pointToClient)
- {
- var sampleIndex = BottomScrollBar.Value + (pointToClient.X - ChartFrameRectangle.Left) / ScaleByUpDown.Value;
- if (ChunkRadioButton.Checked)
- {
- sampleIndex *= _songData.SamplePerChunk;
- }
- var second = sampleIndex / _songData.SampleRate;
- var time = TimeSpan.FromSeconds((double)second);
- return time;
- }
- protected override void OnHandleDestroyed(EventArgs e)
- {
- base.OnHandleDestroyed(e);
- if (DesignMode) return;
- _styleScheme.Dispose();
- foreach (var bmLayer in _bmLayers) bmLayer.Dispose();
- _songData = null;
- }
- private void ChartPictureBox_MouseMove(object sender, MouseEventArgs e)
- {
- if (null != _songData)
- {
- var pointToClient = ChartPictureBox.PointToClient(MousePosition);
- var chartFrameRectangle = ChartFrameRectangle;
- if (chartFrameRectangle.Contains(pointToClient))
- {
- var lastGroup = _currentGroup;
- var sampleTime = GetPointSampleTime(pointToClient);
- _currentGroup = _chunkGroups.FirstOrDefault(p => p.Contains(sampleTime));
- if (lastGroup != _currentGroup)
- {
- _flagUpdates[L2ChunkBars] = true;
- }
- }
- }
- _flagUpdates[L5ActiveOverlay] = true;
- ChartPictureBox.Invalidate();
- }
- private void ChartPictureBox_MouseClick(object sender, MouseEventArgs e)
- {
- var point = e.Location;
- if (null == _songData || !ChartFrameRectangle.Contains(point)) return;
- var t = GetPointSampleTime(point);
- OnSampleClicked(t);
- }
- private void ChartPictureBox_Resize(object sender, EventArgs e)
- {
- if (DesignMode || null == _bmLayers) return;
- var clientSize = ChartPictureBox.ClientSize;
- for (var i = 0; i < MaxLayers; i++)
- {
- var oldBm = _bmLayers[i];
- _bmLayers[i] = new Bitmap(clientSize.Width, clientSize.Height, PixelFormat.Format32bppArgb);
- oldBm?.Dispose();
- _flagUpdates[i] = true;
- }
- UpdateChartScale();
- _flagIgnorePaintRect = true;
- }
- private void ChartPictureBox_Paint(object sender, PaintEventArgs e)
- {
- e.Graphics.SetHighQuility();
- if (DesignMode)
- {
- var rect = ChartFrameRectangle;
- e.Graphics.DrawString(
- $"DesignMode" +
- $"{Environment.NewLine}{Environment.NewLine}" +
- $"{Environment.NewLine}{Environment.NewLine}" +
- $"{Environment.NewLine}{Environment.NewLine}" +
- $"{Environment.NewLine}{Environment.NewLine}" +
- $"{Environment.NewLine}{Environment.NewLine}" +
- $"{Environment.NewLine}{Environment.NewLine}" +
- $"{Environment.NewLine}{Environment.NewLine}" +
- $"{Environment.NewLine}{Environment.NewLine}" +
- $"{Environment.NewLine}{Environment.NewLine}" +
- $"{Environment.NewLine}{Environment.NewLine}" +
- $"{Environment.NewLine}{Environment.NewLine}" +
- $"{Environment.NewLine}{Environment.NewLine}" +
- $"{Environment.NewLine}{Environment.NewLine}" +
- $"{Environment.NewLine}{Environment.NewLine}" +
- $"PreView", Font, Brushes.Black, rect, new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center });
- e.Graphics.DrawRectangle(Pens.Black, rect);
- e.Graphics.DrawLine(Pens.Black, rect.Left, rect.Top, rect.Right, rect.Bottom);
- e.Graphics.DrawLine(Pens.Black, rect.Left, rect.Bottom, rect.Right, rect.Top);
- return;
- }
- //update bg and overlay fg by rect
- for (var i = 0; i < MaxLayers; i++)
- {
- if (false == _flagUpdates[i]) continue;
- try
- {
- _renderActions[i]();
- }
- catch (Exception ex)
- {
- Console.WriteLine(ex);
- }
- _flagUpdates[i] = false;
- }
- if (_flagIgnorePaintRect)
- {
- //paint bg and overlay fg whole
- for (var i = 0; i < MaxLayers; i++) e.Graphics.DrawImage(_bmLayers[i], Point.Empty);
- _flagIgnorePaintRect = false;
- }
- else
- {
- //paint bg and overlay fg by e.ClipRectangle
- var clip = e.ClipRectangle;
- for (var i = 0; i < MaxLayers; i++) e.Graphics.DrawImage(_bmLayers[i], clip, clip, GraphicsUnit.Pixel);
- }
- }
- private void BottomScrollBar_Scroll(object sender, ScrollEventArgs e)
- {
- ReDrawAll();
- }
- private void ScaleByUpDown_ValueChanged(object sender, EventArgs e)
- {
- UpdateChartScale();
- }
- private void SampleRadioButton_CheckedChanged(object sender, EventArgs e)
- {
- UpdateChartScale();
- }
- private class StyleSchemeImpl : InteractiveChartStyleScheme, IDisposable
- {
- private int _frameWidth = 2;
- private int _sampleWidth = 2;
- private int _maCurveWidth = 2;
- private int _cursorWidth = 2;
- private int _playingWidth = 2;
- private Color _frameColor = Color.Black;
- private Color _sampleColor = Color.FromArgb(224, Color.DarkCyan.R, Color.DarkCyan.G, Color.DarkCyan.B);
- private Color _maCurveColor = Color.Blue;
- private Color _chunkColor = Color.FromArgb(128, 255, 0, 0);
- private Color _chunkThresholdColor = Color.FromArgb(128, 255, 0, 255);
- private Color _cursorColor = Color.Black;
- private Color _playingColor = Color.Red;
- //props of basicClass
- public override int ChartFramePadding { get; set; } = 25;
- public override Color FrameColor
- {
- get => _frameColor;
- set
- {
- _frameColor = value;
- _frameBrush?.Dispose();
- _framePen?.Dispose();
- _framePen = null;
- _frameBrush = null;
- }
- }
- public override int FrameWidth
- {
- get => _frameWidth;
- set
- {
- _frameWidth = value;
- _samplePen?.Dispose();
- _samplePen = null;
- }
- }
- public override Color SampleColor
- {
- get => _sampleColor;
- set
- {
- _sampleColor = value;
- _samplePen?.Dispose();
- _samplePen = null;
- }
- }
- public override int SampleWidth
- {
- get => _sampleWidth;
- set
- {
- _sampleWidth = value;
- _samplePen?.Dispose();
- _samplePen = null;
- }
- }
- public override Color ChunkColor
- {
- get => _chunkColor;
- set
- {
- _chunkColor = value;
- _chunkBrush?.Dispose();
- _chunkBrush = null;
- }
- }
- public override Color MaCurveColor
- {
- get => _maCurveColor;
- set
- {
- _maCurveColor = value;
- _maCurvePen?.Dispose();
- _maCurvePen = null;
- }
- }
- public override int MaCurveWidth
- {
- get => _maCurveWidth;
- set
- {
- _maCurveWidth = value;
- _maCurvePen?.Dispose();
- _maCurvePen = null;
- }
- }
- public override Color ChunkThresholdColor
- {
- get => _chunkThresholdColor;
- set
- {
- _chunkThresholdColor = value;
- _chunkThresholdBrush?.Dispose();
- _chunkThresholdBrush = null;
- }
- }
- //fields of gdi stock
- private Pen _framePen;
- private Brush _frameBrush;
- private Pen _samplePen;
- private Pen _maCurvePen;
- private Brush _chunkBrush;
- private Brush _chunkThresholdBrush;
- private Pen _cursorPen;
- private Pen _playingPen;
- //props of gdi stock
- public Pen FramePen => _framePen ?? (_framePen = new Pen(_frameColor, _frameWidth));
- public Brush FrameBrush => _frameBrush ?? (_frameBrush = new SolidBrush(_frameColor));
- public Pen SamplePen => _samplePen ?? (_samplePen = new Pen(_sampleColor, _sampleWidth));
- public Brush ChunkBrush => _chunkBrush ?? (_chunkBrush = new SolidBrush(_chunkColor));
- public Brush ChunkThresholdBrush => _chunkThresholdBrush ?? (_chunkThresholdBrush = new SolidBrush(_chunkThresholdColor));
- public Pen MaCurvePen => _maCurvePen ?? (_maCurvePen = new Pen(_maCurveColor, _maCurveWidth));
- public Pen CursorPen => _cursorPen ?? (_cursorPen = new Pen(_cursorColor, _cursorWidth));
- public Pen PlayingPen => _playingPen ?? (_playingPen = new Pen(_playingColor, _playingWidth));
- public override int CursorWidth
- {
- get => _cursorWidth;
- set
- {
- _cursorWidth = value;
- _playingPen?.Dispose();
- _playingPen = null;
- }
- }
- public override int PlayingWidth
- {
- get => _playingWidth;
- set
- {
- _playingWidth = value;
- _playingPen?.Dispose();
- _playingPen = null;
- }
- }
- public override Color CursorColor
- {
- get => _cursorColor;
- set
- {
- _cursorColor = value;
- _playingPen?.Dispose();
- _playingPen = null;
- }
- }
- public override Color PlayingColor
- {
- get => _playingColor;
- set
- {
- _playingColor = value;
- _playingPen?.Dispose();
- _playingPen = null;
- }
- }
- public void Dispose()
- {
- _framePen?.Dispose();
- _samplePen?.Dispose();
- _maCurvePen?.Dispose();
- _chunkBrush?.Dispose();
- _frameBrush?.Dispose();
- }
- }
- }
- }
|