InteractiveChart.cs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758
  1. using SongVocalSectionAnalyser.AudioAnalysis;
  2. using SongVocalSectionAnalyser.Common;
  3. using SongVocalSectionAnalyser.Common.Collections;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Drawing;
  7. using System.Drawing.Imaging;
  8. using System.Linq;
  9. using System.Windows.Forms;
  10. namespace SongVocalSectionAnalyser.UI
  11. {
  12. public partial class InteractiveChart : UserControl
  13. {
  14. private const int L0Background = 0;
  15. private const int L1Samples = 1;
  16. private const int L2ChunkBars = 2;
  17. private const int L3MaCurve = 3;
  18. private const int L4Fg = 4;
  19. private const int L5ActiveOverlay = 5;
  20. private const int MaxLayers = 6;
  21. private readonly StyleSchemeImpl _styleScheme;
  22. private SongData _songData;
  23. private bool _flagIgnorePaintRect;
  24. private readonly bool[] _flagUpdates;
  25. private readonly Bitmap[] _bmLayers;
  26. private readonly Action[] _renderActions;
  27. private float _maxMa;
  28. private float _maxChunk;
  29. private ChunkGroup[] _chunkGroups;
  30. private ChunkGroup _currentGroup;
  31. private int? _playingSample;
  32. private int ChartFrameWidth => Math.Max(1, ChartPictureBox.ClientSize.Width - _styleScheme.ChartFramePadding * 2);
  33. private int UnitsInChartFrame => (int)Math.Floor(ChartFrameWidth / ScaleByUpDown.Value);
  34. private Rectangle ChartFrameRectangle => new Rectangle(_styleScheme.ChartFramePadding, _styleScheme.ChartFramePadding, ChartFrameWidth, ChartPictureBox.ClientSize.Height - _styleScheme.ChartFramePadding * 2);
  35. public event Action<TimeSpan, ChunkGroup> TimelineClicked;
  36. public InteractiveChartStyleScheme Style => _styleScheme;
  37. public int? PlayingSample
  38. {
  39. get => _playingSample;
  40. set
  41. {
  42. if (null == _playingSample && null == value) return;
  43. if (_playingSample == value) return;
  44. if (null == _playingSample && value.HasValue || _playingSample.HasValue && null == value)
  45. {
  46. _playingSample = value;
  47. _flagUpdates[L5ActiveOverlay] = true;
  48. ChartPictureBox.Invalidate();
  49. return;
  50. }
  51. if (SampleRadioButton.Checked)
  52. {
  53. //todo detect playing sample change scale by sample
  54. }
  55. else
  56. {
  57. var pixelPerSample = 1m / _songData.SamplePerChunk * ScaleByUpDown.Value;
  58. var oldPixel = (int)(_playingSample * pixelPerSample);
  59. var newPixel = (int)(value.Value * pixelPerSample);
  60. _playingSample = value;
  61. if (oldPixel != newPixel)
  62. {
  63. _flagUpdates[L5ActiveOverlay] = true;
  64. ChartPictureBox.Invalidate();
  65. }
  66. }
  67. }
  68. }
  69. public InteractiveChart()
  70. {
  71. _styleScheme = new StyleSchemeImpl();
  72. InitializeComponent();
  73. _flagUpdates = new bool[MaxLayers];
  74. _bmLayers = new Bitmap[MaxLayers];
  75. _renderActions = new Action[]
  76. {
  77. RenderL0Background,
  78. RenderL1Samples,
  79. RenderL2Chunks,
  80. RenderL3MaCurve,
  81. RenderL4Fg,
  82. RenderL5ActiveOverlay,
  83. };
  84. }
  85. public void SetData(SongData data)
  86. {
  87. _songData = data;
  88. if (null == _songData)
  89. {
  90. _chunkGroups = Array.Empty<ChunkGroup>();
  91. _currentGroup = null;
  92. return;
  93. }
  94. _maxChunk = _songData.Chunks.Select(p => p.Value).Max();
  95. _maxMa = _songData.MaLine.Max();
  96. _chunkGroups = _songData.Chunks.GroupContiguous(p => p.OverThreshold)
  97. .Select(p => new ChunkGroup(p.Elements.Min(q => q.Begin), p.Elements.Max(q => q.End))).ToArray();
  98. UpdateChartScale();
  99. }
  100. public TimeSpan GetCurrentPosition()
  101. {
  102. throw new NotImplementedException();
  103. }
  104. public void SetCurrentPosition(TimeSpan position)
  105. {
  106. //if sliding window position changed
  107. // set bg/fg flag both
  108. //if position changed only
  109. // just set fg flag
  110. throw new NotImplementedException();
  111. }
  112. protected virtual void OnSampleClicked(TimeSpan time)
  113. {
  114. TimelineClicked?.Invoke(time, _currentGroup);
  115. }
  116. private void UpdateChartScale()
  117. {
  118. if (null == _songData)
  119. {
  120. //reset
  121. }
  122. else
  123. {
  124. if (SampleRadioButton.Checked)
  125. {
  126. BottomScrollBar.Maximum = _songData.Samples.Count - 1;
  127. }
  128. else
  129. {
  130. BottomScrollBar.Maximum = _songData.Chunks.Count - 1;
  131. }
  132. BottomScrollBar.LargeChange = UnitsInChartFrame;
  133. }
  134. //update all layer
  135. ReDrawAll();
  136. }
  137. private void ReDrawAll()
  138. {
  139. for (var i = 0; i < MaxLayers; i++) _flagUpdates[i] = true;
  140. _flagIgnorePaintRect = true;
  141. ChartPictureBox.Invalidate();
  142. }
  143. private void RenderL0Background()
  144. {
  145. var framePen = _styleScheme.FramePen;
  146. var rcFrame = ChartFrameRectangle;
  147. using (var dc = Graphics.FromImage(_bmLayers[L0Background]))
  148. {
  149. dc.SetHighQuility();
  150. dc.Clear(Color.White);
  151. //draw frame
  152. dc.DrawRectangle(framePen, rcFrame);
  153. //TODO: draw axis and text
  154. // V:sample value
  155. // H:timeline
  156. // HS:time unit
  157. }
  158. }
  159. private void RenderL1Samples()
  160. {
  161. if (null == _songData) return;
  162. var rcFrame = ChartFrameRectangle;
  163. var frameHeightHalf = rcFrame.Height / 2;
  164. var samplePen = _styleScheme.SamplePen;
  165. //TODO: fast render samples
  166. // <2 Per pixel : single points
  167. // 2+ Per pixel : extract max/min value draw vertical line
  168. using (var dc = Graphics.FromImage(_bmLayers[L1Samples]))
  169. {
  170. dc.SetHighQuility();
  171. dc.Clear(Color.Transparent);
  172. var vmY = rcFrame.Top + frameHeightHalf;
  173. if (SampleRadioButton.Checked) //draw scale by sample
  174. {
  175. //draw sample
  176. var sampleIndex = BottomScrollBar.Value;
  177. var pixelRatio = (float)ScaleByUpDown.Value;
  178. var samplePoints = new List<PointF>(ChartFrameWidth);
  179. for (var x = 0; x < UnitsInChartFrame && sampleIndex < _songData.Samples.Count; x++, sampleIndex++)
  180. {
  181. var sampleData = _songData.Samples[sampleIndex];
  182. samplePoints.Add(
  183. new PointF(
  184. rcFrame.Left + x * pixelRatio,
  185. vmY + frameHeightHalf * (sampleData / 32768f)
  186. )
  187. );
  188. }
  189. if (1 < samplePoints.Count) dc.DrawLines(samplePen, samplePoints.ToArray());
  190. }
  191. else //draw scale by chunks
  192. {
  193. //draw samples scale by chunk
  194. {
  195. float sampleIndex = BottomScrollBar.Value * _songData.SamplePerChunk;
  196. var samplesInFrame = UnitsInChartFrame * _songData.SamplePerChunk;
  197. var pixelRatio = rcFrame.Width / (float)samplesInFrame;
  198. var samplePoints = new List<PointF>(ChartFrameWidth);
  199. var step = (float)samplesInFrame / rcFrame.Width / 32;
  200. for (var x = 0f;
  201. x < samplesInFrame && sampleIndex < _songData.Samples.Count;
  202. x += step, sampleIndex += step)
  203. {
  204. var sampleData = _songData.Samples[(int)sampleIndex];
  205. samplePoints.Add(
  206. new PointF(
  207. rcFrame.Left + x * pixelRatio,
  208. vmY + frameHeightHalf * (sampleData / 32768f)
  209. )
  210. );
  211. }
  212. if (1 < samplePoints.Count) dc.DrawLines(samplePen, samplePoints.ToArray());
  213. }
  214. }
  215. }
  216. }
  217. private void RenderL2Chunks()
  218. {
  219. if (null == _songData) return;
  220. const int jump = 15;
  221. var rcFrame = ChartFrameRectangle;
  222. var frameHeightHalf = rcFrame.Height / 2;
  223. var chunkBrush = _styleScheme.ChunkBrush;
  224. var chunkThresholdBrush = _styleScheme.ChunkThresholdBrush;
  225. using (var dc = Graphics.FromImage(_bmLayers[L2ChunkBars]))
  226. {
  227. dc.SetHighQuility();
  228. dc.Clear(Color.Transparent);
  229. if (SampleRadioButton.Checked) //draw scale by sample
  230. {
  231. //TODO: draw chunks bars scale by sample
  232. }
  233. else
  234. {
  235. //draw chunk bars
  236. var chunkIndex = BottomScrollBar.Value;
  237. var pixelRatio = (float)ScaleByUpDown.Value;
  238. for (var x = 0; x < UnitsInChartFrame && chunkIndex < _songData.Chunks.Count; x++, chunkIndex++)
  239. {
  240. var chunkData = _songData.Chunks[chunkIndex];
  241. var pt = new PointF(
  242. rcFrame.Left + x * pixelRatio,
  243. rcFrame.Bottom - frameHeightHalf * (chunkData.Value / _maxChunk));
  244. var barBottom = rcFrame.Bottom;
  245. var sampleTime = GetPointSampleTime(new Point((int)pt.X, (int)pt.Y));
  246. if (true == _currentGroup?.Contains(sampleTime))
  247. {
  248. barBottom += jump;
  249. pt.Y += jump;
  250. }
  251. dc.FillRectangle(chunkData.OverThreshold ? chunkThresholdBrush : chunkBrush
  252. , new RectangleF(pt.X, pt.Y, pixelRatio, barBottom - pt.Y));
  253. if (chunkData.Debounced)
  254. dc.FillRectangle(chunkData.OverThreshold ? chunkThresholdBrush : chunkBrush
  255. , new RectangleF(pt.X, pt.Y, pixelRatio, barBottom - pt.Y));
  256. }
  257. }
  258. }
  259. }
  260. private void RenderL3MaCurve()
  261. {
  262. if (null == _songData) return;
  263. var rcFrame = ChartFrameRectangle;
  264. var frameHeightHalf = rcFrame.Height / 2;
  265. using (var dc = Graphics.FromImage(_bmLayers[L3MaCurve]))
  266. {
  267. dc.SetHighQuility();
  268. dc.Clear(Color.Transparent);
  269. if (SampleRadioButton.Checked) //draw scale by sample
  270. {
  271. //TODO: draw ma curve scale by sample
  272. }
  273. else
  274. {
  275. //draw MA curve
  276. var maIndex = BottomScrollBar.Value;
  277. var samplePixelRatio = (float)ScaleByUpDown.Value;
  278. var maPoints = new List<PointF>(ChartFrameWidth);
  279. for (var x = 0; x < UnitsInChartFrame && maIndex < _songData.MaLine.Count; x++, maIndex++)
  280. {
  281. var maData = _songData.MaLine[maIndex];
  282. maPoints.Add(
  283. new PointF(rcFrame.Left + x * samplePixelRatio
  284. , rcFrame.Bottom - frameHeightHalf * (float)(maData / _maxMa))
  285. );
  286. }
  287. if (1 < maPoints.Count) dc.DrawLines(_styleScheme.MaCurvePen, maPoints.ToArray());
  288. }
  289. }
  290. }
  291. private void RenderL4Fg()
  292. {
  293. var framePen = _styleScheme.FramePen;
  294. var rcFrame = ChartFrameRectangle;
  295. var frameHeightHalf = rcFrame.Height / 2;
  296. var vmY = rcFrame.Top + frameHeightHalf;
  297. using (var dc = Graphics.FromImage(_bmLayers[L4Fg]))
  298. {
  299. // VM:sample value 0
  300. dc.DrawLine(framePen, rcFrame.Left, vmY, rcFrame.Right, vmY);
  301. }
  302. }
  303. private void RenderL5ActiveOverlay()
  304. {
  305. var frameRc = ChartFrameRectangle;
  306. var pointToClient = ChartPictureBox.PointToClient(MousePosition);
  307. using (var dc = Graphics.FromImage(_bmLayers[L5ActiveOverlay]))
  308. {
  309. dc.SetHighQuility();
  310. dc.Clear(Color.Transparent);
  311. if (null != _songData)
  312. {
  313. if (frameRc.Contains(pointToClient))
  314. {
  315. var time = GetPointSampleTime(pointToClient);
  316. dc.DrawString(time.ToStringH2M2S2F2(), Font, _styleScheme.FrameBrush, 0, 0);
  317. if (null != _currentGroup)
  318. {
  319. var chunkGroupIndex = Array.IndexOf(_chunkGroups, _currentGroup);
  320. var chunkInfo = $"#{chunkGroupIndex} {(_currentGroup.End - _currentGroup.Begin).ToStringH2M2S2F2()} [ {_currentGroup.Begin.ToStringH2M2S2F2()} -> {_currentGroup.End.ToStringH2M2S2F2()} ]";
  321. dc.DrawString(chunkInfo, Font, _styleScheme.FrameBrush, new RectangleF(PointF.Empty, ChartPictureBox.ClientSize), new StringFormat() { Alignment = StringAlignment.Far });
  322. }
  323. //mouse vertical bar
  324. dc.DrawLine(_styleScheme.CursorPen,
  325. new Point(pointToClient.X, frameRc.Top),
  326. new Point(pointToClient.X, frameRc.Bottom)
  327. );
  328. }
  329. //playing bar
  330. if (_playingSample.HasValue)
  331. {
  332. if (SampleRadioButton.Checked)
  333. {
  334. //TODO: draw playing bar scale by sample
  335. }
  336. else
  337. {
  338. var windowLeftSample = BottomScrollBar.Value * _songData.SamplePerChunk;
  339. var windowRightSample = windowLeftSample + UnitsInChartFrame * _songData.SamplePerChunk;
  340. if (windowLeftSample <= _playingSample && _playingSample < windowRightSample)
  341. {
  342. var pixelPerSample = 1m / _songData.SamplePerChunk * ScaleByUpDown.Value;
  343. var x = (int)(frameRc.X + (_playingSample - windowLeftSample) * pixelPerSample);
  344. dc.DrawLine(_styleScheme.PlayingPen,
  345. new Point(x, frameRc.Top),
  346. new Point(x, frameRc.Bottom)
  347. );
  348. }
  349. }
  350. }
  351. }
  352. }
  353. }
  354. private TimeSpan GetPointSampleTime(Point pointToClient)
  355. {
  356. var sampleIndex = BottomScrollBar.Value + (pointToClient.X - ChartFrameRectangle.Left) / ScaleByUpDown.Value;
  357. if (ChunkRadioButton.Checked)
  358. {
  359. sampleIndex *= _songData.SamplePerChunk;
  360. }
  361. var second = sampleIndex / _songData.SampleRate;
  362. var time = TimeSpan.FromSeconds((double)second);
  363. return time;
  364. }
  365. protected override void OnHandleDestroyed(EventArgs e)
  366. {
  367. base.OnHandleDestroyed(e);
  368. if (DesignMode) return;
  369. _styleScheme.Dispose();
  370. foreach (var bmLayer in _bmLayers) bmLayer.Dispose();
  371. _songData = null;
  372. }
  373. private void ChartPictureBox_MouseMove(object sender, MouseEventArgs e)
  374. {
  375. if (null != _songData)
  376. {
  377. var pointToClient = ChartPictureBox.PointToClient(MousePosition);
  378. var chartFrameRectangle = ChartFrameRectangle;
  379. if (chartFrameRectangle.Contains(pointToClient))
  380. {
  381. var lastGroup = _currentGroup;
  382. var sampleTime = GetPointSampleTime(pointToClient);
  383. _currentGroup = _chunkGroups.FirstOrDefault(p => p.Contains(sampleTime));
  384. if (lastGroup != _currentGroup)
  385. {
  386. _flagUpdates[L2ChunkBars] = true;
  387. }
  388. }
  389. }
  390. _flagUpdates[L5ActiveOverlay] = true;
  391. ChartPictureBox.Invalidate();
  392. }
  393. private void ChartPictureBox_MouseClick(object sender, MouseEventArgs e)
  394. {
  395. var point = e.Location;
  396. if (null == _songData || !ChartFrameRectangle.Contains(point)) return;
  397. var t = GetPointSampleTime(point);
  398. OnSampleClicked(t);
  399. }
  400. private void ChartPictureBox_Resize(object sender, EventArgs e)
  401. {
  402. if (DesignMode || null == _bmLayers) return;
  403. var clientSize = ChartPictureBox.ClientSize;
  404. for (var i = 0; i < MaxLayers; i++)
  405. {
  406. var oldBm = _bmLayers[i];
  407. _bmLayers[i] = new Bitmap(clientSize.Width, clientSize.Height, PixelFormat.Format32bppArgb);
  408. oldBm?.Dispose();
  409. _flagUpdates[i] = true;
  410. }
  411. UpdateChartScale();
  412. _flagIgnorePaintRect = true;
  413. }
  414. private void ChartPictureBox_Paint(object sender, PaintEventArgs e)
  415. {
  416. e.Graphics.SetHighQuility();
  417. if (DesignMode)
  418. {
  419. var rect = ChartFrameRectangle;
  420. e.Graphics.DrawString(
  421. $"DesignMode" +
  422. $"{Environment.NewLine}{Environment.NewLine}" +
  423. $"{Environment.NewLine}{Environment.NewLine}" +
  424. $"{Environment.NewLine}{Environment.NewLine}" +
  425. $"{Environment.NewLine}{Environment.NewLine}" +
  426. $"{Environment.NewLine}{Environment.NewLine}" +
  427. $"{Environment.NewLine}{Environment.NewLine}" +
  428. $"{Environment.NewLine}{Environment.NewLine}" +
  429. $"{Environment.NewLine}{Environment.NewLine}" +
  430. $"{Environment.NewLine}{Environment.NewLine}" +
  431. $"{Environment.NewLine}{Environment.NewLine}" +
  432. $"{Environment.NewLine}{Environment.NewLine}" +
  433. $"{Environment.NewLine}{Environment.NewLine}" +
  434. $"{Environment.NewLine}{Environment.NewLine}" +
  435. $"{Environment.NewLine}{Environment.NewLine}" +
  436. $"PreView", Font, Brushes.Black, rect, new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center });
  437. e.Graphics.DrawRectangle(Pens.Black, rect);
  438. e.Graphics.DrawLine(Pens.Black, rect.Left, rect.Top, rect.Right, rect.Bottom);
  439. e.Graphics.DrawLine(Pens.Black, rect.Left, rect.Bottom, rect.Right, rect.Top);
  440. return;
  441. }
  442. //update bg and overlay fg by rect
  443. for (var i = 0; i < MaxLayers; i++)
  444. {
  445. if (false == _flagUpdates[i]) continue;
  446. try
  447. {
  448. _renderActions[i]();
  449. }
  450. catch (Exception ex)
  451. {
  452. Console.WriteLine(ex);
  453. }
  454. _flagUpdates[i] = false;
  455. }
  456. if (_flagIgnorePaintRect)
  457. {
  458. //paint bg and overlay fg whole
  459. for (var i = 0; i < MaxLayers; i++) e.Graphics.DrawImage(_bmLayers[i], Point.Empty);
  460. _flagIgnorePaintRect = false;
  461. }
  462. else
  463. {
  464. //paint bg and overlay fg by e.ClipRectangle
  465. var clip = e.ClipRectangle;
  466. for (var i = 0; i < MaxLayers; i++) e.Graphics.DrawImage(_bmLayers[i], clip, clip, GraphicsUnit.Pixel);
  467. }
  468. }
  469. private void BottomScrollBar_Scroll(object sender, ScrollEventArgs e)
  470. {
  471. ReDrawAll();
  472. }
  473. private void ScaleByUpDown_ValueChanged(object sender, EventArgs e)
  474. {
  475. UpdateChartScale();
  476. }
  477. private void SampleRadioButton_CheckedChanged(object sender, EventArgs e)
  478. {
  479. UpdateChartScale();
  480. }
  481. private class StyleSchemeImpl : InteractiveChartStyleScheme, IDisposable
  482. {
  483. private int _frameWidth = 2;
  484. private int _sampleWidth = 2;
  485. private int _maCurveWidth = 2;
  486. private int _cursorWidth = 2;
  487. private int _playingWidth = 2;
  488. private Color _frameColor = Color.Black;
  489. private Color _sampleColor = Color.FromArgb(224, Color.DarkCyan.R, Color.DarkCyan.G, Color.DarkCyan.B);
  490. private Color _maCurveColor = Color.Blue;
  491. private Color _chunkColor = Color.FromArgb(128, 255, 0, 0);
  492. private Color _chunkThresholdColor = Color.FromArgb(128, 255, 0, 255);
  493. private Color _cursorColor = Color.Black;
  494. private Color _playingColor = Color.Red;
  495. //props of basicClass
  496. public override int ChartFramePadding { get; set; } = 25;
  497. public override Color FrameColor
  498. {
  499. get => _frameColor;
  500. set
  501. {
  502. _frameColor = value;
  503. _frameBrush?.Dispose();
  504. _framePen?.Dispose();
  505. _framePen = null;
  506. _frameBrush = null;
  507. }
  508. }
  509. public override int FrameWidth
  510. {
  511. get => _frameWidth;
  512. set
  513. {
  514. _frameWidth = value;
  515. _samplePen?.Dispose();
  516. _samplePen = null;
  517. }
  518. }
  519. public override Color SampleColor
  520. {
  521. get => _sampleColor;
  522. set
  523. {
  524. _sampleColor = value;
  525. _samplePen?.Dispose();
  526. _samplePen = null;
  527. }
  528. }
  529. public override int SampleWidth
  530. {
  531. get => _sampleWidth;
  532. set
  533. {
  534. _sampleWidth = value;
  535. _samplePen?.Dispose();
  536. _samplePen = null;
  537. }
  538. }
  539. public override Color ChunkColor
  540. {
  541. get => _chunkColor;
  542. set
  543. {
  544. _chunkColor = value;
  545. _chunkBrush?.Dispose();
  546. _chunkBrush = null;
  547. }
  548. }
  549. public override Color MaCurveColor
  550. {
  551. get => _maCurveColor;
  552. set
  553. {
  554. _maCurveColor = value;
  555. _maCurvePen?.Dispose();
  556. _maCurvePen = null;
  557. }
  558. }
  559. public override int MaCurveWidth
  560. {
  561. get => _maCurveWidth;
  562. set
  563. {
  564. _maCurveWidth = value;
  565. _maCurvePen?.Dispose();
  566. _maCurvePen = null;
  567. }
  568. }
  569. public override Color ChunkThresholdColor
  570. {
  571. get => _chunkThresholdColor;
  572. set
  573. {
  574. _chunkThresholdColor = value;
  575. _chunkThresholdBrush?.Dispose();
  576. _chunkThresholdBrush = null;
  577. }
  578. }
  579. //fields of gdi stock
  580. private Pen _framePen;
  581. private Brush _frameBrush;
  582. private Pen _samplePen;
  583. private Pen _maCurvePen;
  584. private Brush _chunkBrush;
  585. private Brush _chunkThresholdBrush;
  586. private Pen _cursorPen;
  587. private Pen _playingPen;
  588. //props of gdi stock
  589. public Pen FramePen => _framePen ?? (_framePen = new Pen(_frameColor, _frameWidth));
  590. public Brush FrameBrush => _frameBrush ?? (_frameBrush = new SolidBrush(_frameColor));
  591. public Pen SamplePen => _samplePen ?? (_samplePen = new Pen(_sampleColor, _sampleWidth));
  592. public Brush ChunkBrush => _chunkBrush ?? (_chunkBrush = new SolidBrush(_chunkColor));
  593. public Brush ChunkThresholdBrush => _chunkThresholdBrush ?? (_chunkThresholdBrush = new SolidBrush(_chunkThresholdColor));
  594. public Pen MaCurvePen => _maCurvePen ?? (_maCurvePen = new Pen(_maCurveColor, _maCurveWidth));
  595. public Pen CursorPen => _cursorPen ?? (_cursorPen = new Pen(_cursorColor, _cursorWidth));
  596. public Pen PlayingPen => _playingPen ?? (_playingPen = new Pen(_playingColor, _playingWidth));
  597. public override int CursorWidth
  598. {
  599. get => _cursorWidth;
  600. set
  601. {
  602. _cursorWidth = value;
  603. _playingPen?.Dispose();
  604. _playingPen = null;
  605. }
  606. }
  607. public override int PlayingWidth
  608. {
  609. get => _playingWidth;
  610. set
  611. {
  612. _playingWidth = value;
  613. _playingPen?.Dispose();
  614. _playingPen = null;
  615. }
  616. }
  617. public override Color CursorColor
  618. {
  619. get => _cursorColor;
  620. set
  621. {
  622. _cursorColor = value;
  623. _playingPen?.Dispose();
  624. _playingPen = null;
  625. }
  626. }
  627. public override Color PlayingColor
  628. {
  629. get => _playingColor;
  630. set
  631. {
  632. _playingColor = value;
  633. _playingPen?.Dispose();
  634. _playingPen = null;
  635. }
  636. }
  637. public void Dispose()
  638. {
  639. _framePen?.Dispose();
  640. _samplePen?.Dispose();
  641. _maCurvePen?.Dispose();
  642. _chunkBrush?.Dispose();
  643. _frameBrush?.Dispose();
  644. }
  645. }
  646. }
  647. }