EffectVis.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873
  1. /*
  2. * EffectVis.cpp
  3. * -------------
  4. * Purpose: Implementation of parameter visualisation dialog.
  5. * Notes : (currenlty none)
  6. * Authors: OpenMPT Devs
  7. * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
  8. */
  9. #include "stdafx.h"
  10. #include "Mptrack.h"
  11. #include "Mainfrm.h"
  12. #include "Childfrm.h"
  13. #include "Moddoc.h"
  14. #include "Globals.h"
  15. #include "View_pat.h"
  16. #include "EffectVis.h"
  17. OPENMPT_NAMESPACE_BEGIN
  18. CEffectVis::EditAction CEffectVis::m_nAction = CEffectVis::kAction_OverwriteFX;
  19. IMPLEMENT_DYNAMIC(CEffectVis, CDialog)
  20. CEffectVis::CEffectVis(CViewPattern *pViewPattern, ROWINDEX startRow, ROWINDEX endRow, CHANNELINDEX nchn, CModDoc &modDoc, PATTERNINDEX pat)
  21. : effectInfo(modDoc.GetSoundFile())
  22. , m_ModDoc(modDoc)
  23. , m_SndFile(modDoc.GetSoundFile())
  24. , m_pViewPattern(pViewPattern)
  25. {
  26. m_nFillEffect = effectInfo.GetIndexFromEffect(CMD_SMOOTHMIDI, 0);
  27. m_templatePCNote.Set(NOTE_PCS, 1, 0, 0);
  28. UpdateSelection(startRow, endRow, nchn, pat);
  29. }
  30. BEGIN_MESSAGE_MAP(CEffectVis, CDialog)
  31. ON_WM_ERASEBKGND()
  32. ON_WM_PAINT()
  33. ON_WM_SIZE()
  34. ON_WM_LBUTTONDOWN()
  35. ON_WM_LBUTTONUP()
  36. ON_WM_MOUSEMOVE()
  37. ON_WM_RBUTTONDOWN()
  38. ON_WM_RBUTTONUP()
  39. ON_CBN_SELCHANGE(IDC_VISACTION, &CEffectVis::OnActionChanged)
  40. ON_CBN_SELCHANGE(IDC_VISEFFECTLIST, &CEffectVis::OnEffectChanged)
  41. END_MESSAGE_MAP()
  42. void CEffectVis::DoDataExchange(CDataExchange* pDX)
  43. {
  44. CDialog::DoDataExchange(pDX);
  45. DDX_Control(pDX, IDC_VISSTATUS, m_edVisStatus);
  46. DDX_Control(pDX, IDC_VISEFFECTLIST, m_cmbEffectList);
  47. DDX_Control(pDX, IDC_VISACTION, m_cmbActionList);
  48. }
  49. void CEffectVis::OnActionChanged()
  50. {
  51. m_nAction = static_cast<EditAction>(m_cmbActionList.GetItemData(m_cmbActionList.GetCurSel()));
  52. if (m_nAction == kAction_FillPC
  53. || m_nAction == kAction_OverwritePC
  54. || m_nAction == kAction_Preserve)
  55. m_cmbEffectList.EnableWindow(FALSE);
  56. else
  57. m_cmbEffectList.EnableWindow(TRUE);
  58. }
  59. void CEffectVis::OnEffectChanged()
  60. {
  61. m_nFillEffect = static_cast<UINT>(m_cmbEffectList.GetItemData(m_cmbEffectList.GetCurSel()));
  62. }
  63. void CEffectVis::OnPaint()
  64. {
  65. CPaintDC dc(this); // device context for painting
  66. ShowVis(&dc);
  67. }
  68. uint16 CEffectVis::GetParam(ROWINDEX row) const
  69. {
  70. uint16 paramValue = 0;
  71. if(m_SndFile.Patterns.IsValidPat(m_nPattern))
  72. {
  73. const ModCommand &m = *m_SndFile.Patterns[m_nPattern].GetpModCommand(row, m_nChan);
  74. if (m.IsPcNote())
  75. {
  76. paramValue = m.GetValueEffectCol();
  77. } else
  78. {
  79. paramValue = m.param;
  80. }
  81. }
  82. return paramValue;
  83. }
  84. // Sets a row's param value based on the vertical cursor position.
  85. // Sets either plain pattern effect parameter or PC note parameter
  86. // as appropriate, depending on contents of row.
  87. void CEffectVis::SetParamFromY(ROWINDEX row, int y)
  88. {
  89. if(!m_SndFile.Patterns.IsValidPat(m_nPattern))
  90. return;
  91. ModCommand &m = *m_SndFile.Patterns[m_nPattern].GetpModCommand(row, m_nChan);
  92. if (IsPcNote(row))
  93. {
  94. uint16 param = ScreenYToPCParam(y);
  95. m.SetValueEffectCol(param);
  96. } else
  97. {
  98. ModCommand::PARAM param = ScreenYToFXParam(y);
  99. // Cap the parameter value as appropriate, based on effect type (e.g. Zxx gets capped to [0x00,0x7F])
  100. effectInfo.GetEffectFromIndex(effectInfo.GetIndexFromEffect(m.command, param), param);
  101. m.param = param;
  102. }
  103. }
  104. EffectCommand CEffectVis::GetCommand(ROWINDEX row) const
  105. {
  106. if(m_SndFile.Patterns.IsValidPat(m_nPattern))
  107. return static_cast<EffectCommand>(m_SndFile.Patterns[m_nPattern].GetpModCommand(row, m_nChan)->command);
  108. else
  109. return CMD_NONE;
  110. }
  111. void CEffectVis::SetCommand(ROWINDEX row, EffectCommand command)
  112. {
  113. if(m_SndFile.Patterns.IsValidPat(m_nPattern))
  114. {
  115. ModCommand &m = *m_SndFile.Patterns[m_nPattern].GetpModCommand(row, m_nChan);
  116. if(m.IsPcNote())
  117. {
  118. // Clear PC note
  119. m.note = 0;
  120. m.instr = 0;
  121. m.volcmd = VOLCMD_NONE;
  122. m.vol = 0;
  123. }
  124. m.command = command;
  125. }
  126. }
  127. int CEffectVis::RowToScreenX(ROWINDEX row) const
  128. {
  129. if ((row >= m_startRow) || (row <= m_endRow))
  130. return mpt::saturate_round<int>(m_rcDraw.left + m_innerBorder + (row - m_startRow) * m_pixelsPerRow);
  131. return -1;
  132. }
  133. int CEffectVis::RowToScreenY(ROWINDEX row) const
  134. {
  135. int screenY = -1;
  136. if(m_SndFile.Patterns.IsValidPat(m_nPattern))
  137. {
  138. const ModCommand &m = *m_SndFile.Patterns[m_nPattern].GetpModCommand(row, m_nChan);
  139. if (m.IsPcNote())
  140. {
  141. uint16 paramValue = m.GetValueEffectCol();
  142. screenY = PCParamToScreenY(paramValue);
  143. } else
  144. {
  145. uint16 paramValue = m.param;
  146. screenY = FXParamToScreenY(paramValue);
  147. }
  148. }
  149. return screenY;
  150. }
  151. int CEffectVis::FXParamToScreenY(uint16 param) const
  152. {
  153. if(param >= 0x00 && param <= 0xFF)
  154. return mpt::saturate_round<int>(m_rcDraw.bottom - param * m_pixelsPerFXParam);
  155. return -1;
  156. }
  157. int CEffectVis::PCParamToScreenY(uint16 param) const
  158. {
  159. if(param >= 0x00 && param <= ModCommand::maxColumnValue)
  160. return mpt::saturate_round<int>(m_rcDraw.bottom - param*m_pixelsPerPCParam);
  161. return -1;
  162. }
  163. ModCommand::PARAM CEffectVis::ScreenYToFXParam(int y) const
  164. {
  165. if(y <= FXParamToScreenY(0xFF))
  166. return 0xFF;
  167. if(y >= FXParamToScreenY(0x00))
  168. return 0x00;
  169. return mpt::saturate_round<ModCommand::PARAM>((m_rcDraw.bottom - y) / m_pixelsPerFXParam);
  170. }
  171. uint16 CEffectVis::ScreenYToPCParam(int y) const
  172. {
  173. if(y <= PCParamToScreenY(ModCommand::maxColumnValue))
  174. return ModCommand::maxColumnValue;
  175. if(y >= PCParamToScreenY(0x00))
  176. return 0x00;
  177. return mpt::saturate_round<uint16>((m_rcDraw.bottom - y) / m_pixelsPerPCParam);
  178. }
  179. ROWINDEX CEffectVis::ScreenXToRow(int x) const
  180. {
  181. if(x <= RowToScreenX(m_startRow))
  182. return m_startRow;
  183. if(x >= RowToScreenX(m_endRow))
  184. return m_endRow;
  185. return mpt::saturate_round<ROWINDEX>(m_startRow + (x - m_innerBorder) / m_pixelsPerRow);
  186. }
  187. void CEffectVis::DrawGrid()
  188. {
  189. // Lots of room for optimisation here.
  190. // Draw vertical grid lines
  191. ROWINDEX nBeat = m_SndFile.m_nDefaultRowsPerBeat, nMeasure = m_SndFile.m_nDefaultRowsPerMeasure;
  192. if(m_SndFile.Patterns[m_nPattern].GetOverrideSignature())
  193. {
  194. nBeat = m_SndFile.Patterns[m_nPattern].GetRowsPerBeat();
  195. nMeasure = m_SndFile.Patterns[m_nPattern].GetRowsPerMeasure();
  196. }
  197. m_dcGrid.FillSolidRect(&m_rcDraw, 0);
  198. auto oldPen = m_dcGrid.SelectStockObject(DC_PEN);
  199. for(ROWINDEX row = m_startRow; row <= m_endRow; row++)
  200. {
  201. if(row % nMeasure == 0)
  202. m_dcGrid.SetDCPenColor(RGB(0xFF, 0xFF, 0xFF));
  203. else if(row % nBeat == 0)
  204. m_dcGrid.SetDCPenColor(RGB(0x99, 0x99, 0x99));
  205. else
  206. m_dcGrid.SetDCPenColor(RGB(0x55, 0x55, 0x55));
  207. int x1 = RowToScreenX(row);
  208. m_dcGrid.MoveTo(x1, m_rcDraw.top);
  209. m_dcGrid.LineTo(x1, m_rcDraw.bottom);
  210. }
  211. // Draw horizontal grid lines
  212. constexpr UINT numHorizontalLines = 4;
  213. for(UINT i = 0; i < numHorizontalLines; i++)
  214. {
  215. COLORREF c = 0;
  216. switch(i % 4)
  217. {
  218. case 0: c = RGB(0x00, 0x00, 0x00); break;
  219. case 1: c = RGB(0x40, 0x40, 0x40); break;
  220. case 2: c = RGB(0x80, 0x80, 0x80); break;
  221. case 3: c = RGB(0xCC, 0xCC, 0xCC); break;
  222. }
  223. m_dcGrid.SetDCPenColor(c);
  224. int y1 = m_rcDraw.bottom / numHorizontalLines * i;
  225. m_dcGrid.MoveTo(m_rcDraw.left + m_innerBorder, y1);
  226. m_dcGrid.LineTo(m_rcDraw.right - m_innerBorder, y1);
  227. }
  228. m_dcGrid.SelectObject(oldPen);
  229. }
  230. void CEffectVis::SetPlayCursor(PATTERNINDEX nPat, ROWINDEX nRow)
  231. {
  232. if(nPat == m_nPattern && nRow == m_nOldPlayPos)
  233. return;
  234. if(m_nOldPlayPos >= m_startRow && m_nOldPlayPos <= m_endRow)
  235. {
  236. // erase current playpos
  237. int x1 = RowToScreenX(m_nOldPlayPos);
  238. m_dcPlayPos.SelectStockObject(BLACK_PEN);
  239. m_dcPlayPos.MoveTo(x1,m_rcDraw.top);
  240. m_dcPlayPos.LineTo(x1,m_rcDraw.bottom);
  241. }
  242. if((nRow < m_startRow) || (nRow > m_endRow) || (nPat != m_nPattern))
  243. return;
  244. int x1 = RowToScreenX(nRow);
  245. m_dcPlayPos.SelectStockObject(DC_PEN);
  246. m_dcPlayPos.SetDCPenColor(TrackerSettings::Instance().rgbCustomColors[MODCOLOR_SAMPLE]);
  247. m_dcPlayPos.MoveTo(x1,m_rcDraw.top);
  248. m_dcPlayPos.LineTo(x1,m_rcDraw.bottom);
  249. m_nOldPlayPos = nRow;
  250. InvalidateRect(NULL, FALSE);
  251. }
  252. void CEffectVis::ShowVis(CDC *pDC)
  253. {
  254. if (m_forceRedraw)
  255. {
  256. m_forceRedraw = false;
  257. // if we already have a memory dc, destroy it (this occurs for a re-size)
  258. if (m_dcGrid.m_hDC)
  259. {
  260. m_dcGrid.SelectObject(m_pbOldGrid);
  261. m_dcGrid.DeleteDC();
  262. m_dcNodes.SelectObject(m_pbOldNodes);
  263. m_dcNodes.DeleteDC();
  264. m_dcPlayPos.SelectObject(m_pbOldPlayPos);
  265. m_dcPlayPos.DeleteDC();
  266. m_bPlayPos.DeleteObject();
  267. m_bGrid.DeleteObject();
  268. m_bNodes.DeleteObject();
  269. }
  270. // create a memory based dc for drawing the grid
  271. m_dcGrid.CreateCompatibleDC(pDC);
  272. m_bGrid.CreateCompatibleBitmap(pDC, m_rcDraw.Width(), m_rcDraw.Height());
  273. m_pbOldGrid = *m_dcGrid.SelectObject(&m_bGrid);
  274. // create a memory based dc for drawing the nodes
  275. m_dcNodes.CreateCompatibleDC(pDC);
  276. m_bNodes.CreateCompatibleBitmap(pDC, m_rcDraw.Width(), m_rcDraw.Height());
  277. m_pbOldNodes = *m_dcNodes.SelectObject(&m_bNodes);
  278. // create a memory based dc for drawing the nodes
  279. m_dcPlayPos.CreateCompatibleDC(pDC);
  280. m_bPlayPos.CreateCompatibleBitmap(pDC, m_rcDraw.Width(), m_rcDraw.Height());
  281. m_pbOldPlayPos = *m_dcPlayPos.SelectObject(&m_bPlayPos);
  282. SetPlayCursor(m_nPattern, m_nOldPlayPos);
  283. DrawGrid();
  284. DrawNodes();
  285. }
  286. // display the new image, combining the nodes with the grid
  287. ShowVisImage(pDC);
  288. }
  289. void CEffectVis::ShowVisImage(CDC *pDC)
  290. {
  291. // to avoid flicker, establish a memory dc, draw to it
  292. // and then BitBlt it to the destination "pDC"
  293. CDC memDC;
  294. memDC.CreateCompatibleDC(pDC);
  295. if (!memDC)
  296. return;
  297. CBitmap memBitmap;
  298. memBitmap.CreateCompatibleBitmap(pDC, m_rcDraw.Width(), m_rcDraw.Height());
  299. CBitmap *oldBitmap = memDC.SelectObject(&memBitmap);
  300. // make sure we have the bitmaps
  301. if (!m_dcGrid.m_hDC)
  302. return;
  303. if (!m_dcNodes.m_hDC)
  304. return;
  305. if (!m_dcPlayPos.m_hDC)
  306. return;
  307. if (memDC.m_hDC != nullptr)
  308. {
  309. // draw the grid
  310. memDC.BitBlt(0, 0, m_rcDraw.Width(), m_rcDraw.Height(), &m_dcGrid, 0, 0, SRCCOPY);
  311. // merge the nodes image with the grid
  312. memDC.TransparentBlt(0, 0, m_rcDraw.Width(), m_rcDraw.Height(), &m_dcNodes, 0, 0, m_rcDraw.Width(), m_rcDraw.Height(), 0x00000000);
  313. // further merge the playpos
  314. memDC.TransparentBlt(0, 0, m_rcDraw.Width(), m_rcDraw.Height(), &m_dcPlayPos, 0, 0, m_rcDraw.Width(), m_rcDraw.Height(), 0x00000000);
  315. // copy the resulting bitmap to the destination
  316. pDC->BitBlt(0, 0, m_rcDraw.Width(), m_rcDraw.Height(), &memDC, 0, 0, SRCCOPY);
  317. }
  318. memDC.SelectObject(oldBitmap);
  319. }
  320. void CEffectVis::DrawNodes()
  321. {
  322. if(m_rcDraw.IsRectEmpty())
  323. return;
  324. //Draw
  325. const int lineWidth = Util::ScalePixels(1, m_hWnd);
  326. const int nodeSizeHalf = m_nodeSizeHalf;
  327. const int nodeSizeHalf2 = nodeSizeHalf - lineWidth + 1;
  328. const int nodeSize = 2 * nodeSizeHalf + 1;
  329. //erase
  330. if ((ROWINDEX)m_nRowToErase < m_startRow || m_nParamToErase < 0)
  331. {
  332. m_dcNodes.FillSolidRect(&m_rcDraw, 0);
  333. } else
  334. {
  335. int x = RowToScreenX(m_nRowToErase);
  336. CRect r(x - nodeSizeHalf, m_rcDraw.top, x + nodeSizeHalf + 1, m_rcDraw.bottom);
  337. m_dcNodes.FillSolidRect(&r, 0);
  338. }
  339. for (ROWINDEX row = m_startRow; row <= m_endRow; row++)
  340. {
  341. COLORREF col = IsPcNote(row) ? RGB(0xFF, 0xFF, 0x00) : RGB(0xD0, 0xFF, 0xFF);
  342. int x = RowToScreenX(row);
  343. int y = RowToScreenY(row);
  344. m_dcNodes.FillSolidRect(x - nodeSizeHalf, y - nodeSizeHalf, nodeSize, lineWidth, col); // Top
  345. m_dcNodes.FillSolidRect(x + nodeSizeHalf2, y - nodeSizeHalf, lineWidth, nodeSize, col); // Right
  346. m_dcNodes.FillSolidRect(x - nodeSizeHalf, y + nodeSizeHalf2, nodeSize, lineWidth, col); // Bottom
  347. m_dcNodes.FillSolidRect(x - nodeSizeHalf, y - nodeSizeHalf, lineWidth, nodeSize, col); // Left
  348. }
  349. }
  350. void CEffectVis::InvalidateRow(int row)
  351. {
  352. if (((UINT)row < m_startRow) || ((UINT)row > m_endRow)) return;
  353. //It seems this optimisation doesn't work properly yet. Disable in Update()
  354. int x = RowToScreenX(row);
  355. invalidated.bottom = m_rcDraw.bottom;
  356. invalidated.top = m_rcDraw.top;
  357. invalidated.left = x - m_nodeSizeHalf;
  358. invalidated.right = x + m_nodeSizeHalf + 1;
  359. InvalidateRect(&invalidated, FALSE);
  360. }
  361. void CEffectVis::OpenEditor(CWnd *parent)
  362. {
  363. Create(IDD_EFFECTVISUALIZER, parent);
  364. m_forceRedraw = true;
  365. if(TrackerSettings::Instance().effectVisWidth > 0 && TrackerSettings::Instance().effectVisHeight > 0)
  366. {
  367. WINDOWPLACEMENT wnd;
  368. wnd.length = sizeof(wnd);
  369. GetWindowPlacement(&wnd);
  370. wnd.showCmd = SW_SHOWNOACTIVATE;
  371. CRect rect = wnd.rcNormalPosition;
  372. if(TrackerSettings::Instance().effectVisX > int32_min && TrackerSettings::Instance().effectVisY > int32_min)
  373. {
  374. CRect mainRect;
  375. CMainFrame::GetMainFrame()->GetWindowRect(mainRect);
  376. rect.left = mainRect.left + MulDiv(TrackerSettings::Instance().effectVisX, Util::GetDPIx(m_hWnd), 96);
  377. rect.top = mainRect.top + MulDiv(TrackerSettings::Instance().effectVisY, Util::GetDPIx(m_hWnd), 96);
  378. }
  379. rect.right = rect.left + MulDiv(TrackerSettings::Instance().effectVisWidth, Util::GetDPIx(m_hWnd), 96);
  380. rect.bottom = rect.top + MulDiv(TrackerSettings::Instance().effectVisHeight, Util::GetDPIx(m_hWnd), 96);
  381. wnd.rcNormalPosition = rect;
  382. SetWindowPlacement(&wnd);
  383. }
  384. ShowWindow(SW_SHOW);
  385. }
  386. void CEffectVis::OnClose()
  387. {
  388. DoClose();
  389. }
  390. void CEffectVis::OnOK()
  391. {
  392. OnClose();
  393. }
  394. void CEffectVis::OnCancel()
  395. {
  396. OnClose();
  397. }
  398. void CEffectVis::DoClose()
  399. {
  400. WINDOWPLACEMENT wnd;
  401. wnd.length = sizeof(wnd);
  402. GetWindowPlacement(&wnd);
  403. CRect mainRect;
  404. CMainFrame::GetMainFrame()->GetWindowRect(mainRect);
  405. CRect rect = wnd.rcNormalPosition;
  406. rect.MoveToXY(rect.left - mainRect.left, rect.top - mainRect.top);
  407. TrackerSettings::Instance().effectVisWidth = MulDiv(rect.Width(), 96, Util::GetDPIx(m_hWnd));
  408. TrackerSettings::Instance().effectVisHeight = MulDiv(rect.Height(), 96, Util::GetDPIy(m_hWnd));
  409. TrackerSettings::Instance().effectVisX = MulDiv(rect.left, 96, Util::GetDPIx(m_hWnd));
  410. TrackerSettings::Instance().effectVisY = MulDiv(rect.top, 96, Util::GetDPIy(m_hWnd));
  411. m_dcGrid.SelectObject(m_pbOldGrid);
  412. m_dcGrid.DeleteDC();
  413. m_dcNodes.SelectObject(m_pbOldNodes);
  414. m_dcNodes.DeleteDC();
  415. m_dcPlayPos.SelectObject(m_pbOldPlayPos);
  416. m_dcPlayPos.DeleteDC();
  417. m_bGrid.DeleteObject();
  418. m_bNodes.DeleteObject();
  419. m_bPlayPos.DeleteObject();
  420. DestroyWindow();
  421. }
  422. void CEffectVis::PostNcDestroy()
  423. {
  424. m_pViewPattern->m_pEffectVis = nullptr;
  425. }
  426. void CEffectVis::OnSize(UINT nType, int cx, int cy)
  427. {
  428. MPT_UNREFERENCED_PARAMETER(nType);
  429. MPT_UNREFERENCED_PARAMETER(cx);
  430. MPT_UNREFERENCED_PARAMETER(cy);
  431. GetClientRect(&m_rcFullWin);
  432. m_rcDraw.SetRect(m_rcFullWin.left, m_rcFullWin.top, m_rcFullWin.right, m_rcFullWin.bottom - m_marginBottom);
  433. const int actionListWidth = Util::ScalePixels(170, m_hWnd);
  434. const int commandListWidth = Util::ScalePixels(160, m_hWnd);
  435. if (IsWindow(m_edVisStatus.m_hWnd))
  436. m_edVisStatus.SetWindowPos(this, m_rcFullWin.left, m_rcDraw.bottom, m_rcFullWin.right-commandListWidth-actionListWidth, m_rcFullWin.bottom-m_rcDraw.bottom, SWP_NOACTIVATE|SWP_NOCOPYBITS|SWP_SHOWWINDOW|SWP_NOZORDER);
  437. if (IsWindow(m_cmbActionList))
  438. m_cmbActionList.SetWindowPos(this, m_rcFullWin.right-commandListWidth-actionListWidth, m_rcDraw.bottom, actionListWidth, m_rcFullWin.bottom-m_rcDraw.bottom, SWP_NOACTIVATE|SWP_NOCOPYBITS|SWP_SHOWWINDOW|SWP_NOZORDER);
  439. if (IsWindow(m_cmbEffectList))
  440. m_cmbEffectList.SetWindowPos(this, m_rcFullWin.right-commandListWidth, m_rcDraw.bottom, commandListWidth, m_rcFullWin.bottom-m_rcDraw.bottom, SWP_NOACTIVATE|SWP_NOCOPYBITS|SWP_SHOWWINDOW|SWP_NOZORDER);
  441. if(m_nRows)
  442. m_pixelsPerRow = (float)(m_rcDraw.Width() - m_innerBorder * 2) / (float)m_nRows;
  443. else
  444. m_pixelsPerRow = 1;
  445. m_pixelsPerFXParam = (float)(m_rcDraw.Height())/(float)0xFF;
  446. m_pixelsPerPCParam = (float)(m_rcDraw.Height())/(float)ModCommand::maxColumnValue;
  447. m_forceRedraw = true;
  448. InvalidateRect(NULL, FALSE); //redraw everything
  449. }
  450. void CEffectVis::Update()
  451. {
  452. DrawNodes();
  453. if (::IsWindow(m_hWnd))
  454. {
  455. OnPaint();
  456. if (m_nRowToErase<0)
  457. InvalidateRect(NULL, FALSE); // redraw everything
  458. else
  459. {
  460. InvalidateRow(m_nRowToErase);
  461. m_nParamToErase=-1;
  462. m_nRowToErase=-1;
  463. }
  464. }
  465. }
  466. void CEffectVis::UpdateSelection(ROWINDEX startRow, ROWINDEX endRow, CHANNELINDEX nchn, PATTERNINDEX pat)
  467. {
  468. m_startRow = startRow;
  469. m_endRow = endRow;
  470. m_nRows = endRow - startRow;
  471. m_nChan = nchn;
  472. m_nPattern = pat;
  473. //Check pattern, start row and channel exist
  474. if(!m_SndFile.Patterns.IsValidPat(m_nPattern) || !m_SndFile.Patterns[m_nPattern].IsValidRow(m_startRow) || m_nChan >= m_SndFile.GetNumChannels())
  475. {
  476. DoClose();
  477. return;
  478. }
  479. //Check end exists
  480. if(!m_SndFile.Patterns[m_nPattern].IsValidRow(m_endRow))
  481. {
  482. m_endRow = m_SndFile.Patterns[m_nPattern].GetNumRows() - 1;
  483. }
  484. if(m_nRows)
  485. m_pixelsPerRow = (float)(m_rcDraw.Width() - m_innerBorder * 2) / (float)m_nRows;
  486. else
  487. m_pixelsPerRow = 1;
  488. m_pixelsPerFXParam = (float)(m_rcDraw.Height())/(float)0xFF;
  489. m_pixelsPerPCParam = (float)(m_rcDraw.Height())/(float)ModCommand::maxColumnValue;
  490. m_forceRedraw = true;
  491. Update();
  492. }
  493. void CEffectVis::OnRButtonDown(UINT nFlags, CPoint point)
  494. {
  495. if (!(m_dwStatus & FXVSTATUS_LDRAGGING))
  496. {
  497. SetFocus();
  498. SetCapture();
  499. m_nDragItem = ScreenXToRow(point.x);
  500. m_dwStatus |= FXVSTATUS_RDRAGGING;
  501. m_ModDoc.GetPatternUndo().PrepareUndo(static_cast<PATTERNINDEX>(m_nPattern), m_nChan, m_nDragItem, 1, 1, "Parameter Editor entry");
  502. OnMouseMove(nFlags, point);
  503. }
  504. CDialog::OnRButtonDown(nFlags, point);
  505. }
  506. void CEffectVis::OnRButtonUp(UINT nFlags, CPoint point)
  507. {
  508. ReleaseCapture();
  509. m_dwStatus = 0x00;
  510. m_nDragItem = -1;
  511. CDialog::OnRButtonUp(nFlags, point);
  512. }
  513. void CEffectVis::OnMouseMove(UINT nFlags, CPoint point)
  514. {
  515. CDialog::OnMouseMove(nFlags, point);
  516. ROWINDEX row = ScreenXToRow(point.x);
  517. if ((m_dwStatus & FXVSTATUS_RDRAGGING) && (m_nDragItem>=0) )
  518. {
  519. m_nRowToErase = m_nDragItem;
  520. m_nParamToErase = GetParam(m_nDragItem);
  521. MakeChange(m_nDragItem, point.y);
  522. } else if ((m_dwStatus & FXVSTATUS_LDRAGGING))
  523. {
  524. // Interpolate if we detect that rows have been skipped but the left mouse button was not released.
  525. // This ensures we produce a smooth curve even when we are not notified of mouse movements at a high frequency (e.g. if CPU usage is high)
  526. const int steps = std::abs((int)row - (int)m_nLastDrawnRow);
  527. if (m_nLastDrawnRow != ROWINDEX_INVALID && m_nLastDrawnRow > m_startRow && steps > 1)
  528. {
  529. int direction = ((int)(row - m_nLastDrawnRow) > 0) ? 1 : -1;
  530. float factor = (float)(point.y - m_nLastDrawnY)/(float)steps + 0.5f;
  531. int currentRow;
  532. for (int i=1; i<=steps; i++)
  533. {
  534. currentRow = m_nLastDrawnRow+(direction*i);
  535. int interpolatedY = mpt::saturate_round<int>(m_nLastDrawnY + ((float)i * factor));
  536. MakeChange(currentRow, interpolatedY);
  537. }
  538. //Don't use single value update
  539. m_nRowToErase = -1;
  540. m_nParamToErase = -1;
  541. } else
  542. {
  543. m_nRowToErase = -1;
  544. m_nParamToErase = -1;
  545. MakeChange(row, point.y);
  546. }
  547. // Remember last modified point in case we need to interpolate
  548. m_nLastDrawnRow = row;
  549. m_nLastDrawnY = point.y;
  550. }
  551. //update status bar
  552. CString status;
  553. CString effectName;
  554. uint16 paramValue;
  555. if (IsPcNote(row))
  556. {
  557. paramValue = ScreenYToPCParam(point.y);
  558. effectName.Format(_T("%s"), _T("Param Control")); // TODO - show smooth & plug+param
  559. } else
  560. {
  561. paramValue = ScreenYToFXParam(point.y);
  562. effectInfo.GetEffectInfo(effectInfo.GetIndexFromEffect(GetCommand(row), ModCommand::PARAM(GetParam(row))), &effectName, true);
  563. }
  564. status.Format(_T("Pat: %d\tChn: %d\tRow: %d\tVal: %02X (%03d) [%s]"),
  565. m_nPattern, m_nChan+1, static_cast<signed int>(row), paramValue, paramValue, effectName.GetString());
  566. m_edVisStatus.SetWindowText(status);
  567. }
  568. void CEffectVis::OnLButtonDown(UINT nFlags, CPoint point)
  569. {
  570. if (!(m_dwStatus & FXVSTATUS_RDRAGGING))
  571. {
  572. SetFocus();
  573. SetCapture();
  574. m_nDragItem = ScreenXToRow(point.x);
  575. m_dwStatus |= FXVSTATUS_LDRAGGING;
  576. m_ModDoc.GetPatternUndo().PrepareUndo(static_cast<PATTERNINDEX>(m_nPattern), m_nChan, m_startRow, 1, m_endRow - m_startRow + 1, "Parameter Editor entry");
  577. OnMouseMove(nFlags, point);
  578. }
  579. CDialog::OnLButtonDown(nFlags, point);
  580. }
  581. void CEffectVis::OnLButtonUp(UINT nFlags, CPoint point)
  582. {
  583. ReleaseCapture();
  584. m_dwStatus = 0x00;
  585. CDialog::OnLButtonUp(nFlags, point);
  586. m_nLastDrawnRow = ROWINDEX_INVALID;
  587. }
  588. BOOL CEffectVis::OnInitDialog()
  589. {
  590. CDialog::OnInitDialog();
  591. int dpi = Util::GetDPIx(m_hWnd);
  592. m_nodeSizeHalf = MulDiv(3, dpi, 96);
  593. m_marginBottom = MulDiv(20, dpi, 96);
  594. m_innerBorder = MulDiv(4, dpi, 96);
  595. // If first selected row is a PC event (or some other row but there aren't any other effects), default to PC note overwrite mode
  596. // and use it as a template for new PC notes that will be created via the visualiser.
  597. bool isPCevent = IsPcNote(m_startRow);
  598. if(!isPCevent)
  599. {
  600. for(ROWINDEX row = m_startRow; row <= m_endRow; row++)
  601. {
  602. if(IsPcNote(row))
  603. {
  604. isPCevent = true;
  605. } else if(GetCommand(row) != CMD_NONE)
  606. {
  607. isPCevent = false;
  608. break;
  609. }
  610. }
  611. }
  612. if(m_ModDoc.GetModType() == MOD_TYPE_MPT && isPCevent)
  613. {
  614. m_nAction = kAction_OverwritePC;
  615. if(m_SndFile.Patterns.IsValidPat(m_nPattern))
  616. {
  617. ModCommand &m = *m_SndFile.Patterns[m_nPattern].GetpModCommand(m_startRow, m_nChan);
  618. m_templatePCNote.Set(m.note, m.instr, m.GetValueVolCol(), 0);
  619. }
  620. m_cmbEffectList.EnableWindow(FALSE);
  621. } else
  622. {
  623. // Otherwise, default to FX overwrite and
  624. // use effect of first selected row as default effect type
  625. m_nAction = kAction_OverwriteFX;
  626. m_nFillEffect = effectInfo.GetIndexFromEffect(GetCommand(m_startRow), ModCommand::PARAM(GetParam(m_startRow)));
  627. if (m_nFillEffect < 0 || m_nFillEffect >= MAX_EFFECTS)
  628. m_nFillEffect = effectInfo.GetIndexFromEffect(CMD_SMOOTHMIDI, 0);
  629. }
  630. CString s;
  631. UINT numfx = effectInfo.GetNumEffects();
  632. m_cmbEffectList.ResetContent();
  633. int k;
  634. for (UINT i=0; i<numfx; i++)
  635. {
  636. if (effectInfo.GetEffectInfo(i, &s, true))
  637. {
  638. k =m_cmbEffectList.AddString(s);
  639. m_cmbEffectList.SetItemData(k, i);
  640. if ((int)i == m_nFillEffect)
  641. m_cmbEffectList.SetCurSel(k);
  642. }
  643. }
  644. m_cmbActionList.ResetContent();
  645. m_cmbActionList.SetItemData(m_cmbActionList.AddString(_T("Overwrite with effect:")), kAction_OverwriteFX);
  646. m_cmbActionList.SetItemData(m_cmbActionList.AddString(_T("Overwrite effect next to note:")), kAction_OverwriteFXWithNote);
  647. m_cmbActionList.SetItemData(m_cmbActionList.AddString(_T("Fill blanks with effect:")), kAction_FillFX);
  648. if (m_ModDoc.GetModType() == MOD_TYPE_MPT)
  649. {
  650. m_cmbActionList.SetItemData(m_cmbActionList.AddString(_T("Overwrite with PC note")), kAction_OverwritePC);
  651. m_cmbActionList.SetItemData(m_cmbActionList.AddString(_T("Fill blanks with PC note")), kAction_FillPC);
  652. }
  653. m_cmbActionList.SetItemData(m_cmbActionList.AddString(_T("Never change effect type")), kAction_Preserve);
  654. m_cmbActionList.SetCurSel(m_nAction);
  655. return true;
  656. }
  657. void CEffectVis::MakeChange(ROWINDEX row, int y)
  658. {
  659. if(!m_SndFile.Patterns.IsValidPat(m_nPattern))
  660. return;
  661. ModCommand &m = *m_SndFile.Patterns[m_nPattern].GetpModCommand(row, m_nChan);
  662. switch (m_nAction)
  663. {
  664. case kAction_FillFX:
  665. // Only set command if there isn't a command already at this row and it's not a PC note
  666. if (GetCommand(row) == CMD_NONE && !IsPcNote(row))
  667. {
  668. SetCommand(row, effectInfo.GetEffectFromIndex(m_nFillEffect));
  669. }
  670. // Always set param
  671. SetParamFromY(row, y);
  672. break;
  673. case kAction_OverwriteFXWithNote:
  674. if(!m_SndFile.Patterns.IsValidPat(m_nPattern))
  675. break;
  676. if(!m_SndFile.Patterns[m_nPattern].GetpModCommand(row, m_nChan)->IsNote())
  677. break;
  678. [[fallthrough]];
  679. case kAction_OverwriteFX:
  680. // Always set command and param. Blows away any PC notes.
  681. SetCommand(row, effectInfo.GetEffectFromIndex(m_nFillEffect));
  682. SetParamFromY(row, y);
  683. break;
  684. case kAction_FillPC:
  685. // Fill only empty slots with PC notes - leave other slots alone.
  686. if (m.IsEmpty())
  687. {
  688. SetPcNote(row);
  689. }
  690. // Always set param
  691. SetParamFromY(row, y);
  692. break;
  693. case kAction_OverwritePC:
  694. // Always convert to PC Note and set param value
  695. SetPcNote(row);
  696. SetParamFromY(row, y);
  697. break;
  698. case kAction_Preserve:
  699. if (GetCommand(row) != CMD_NONE || IsPcNote(row))
  700. {
  701. // Only set param if we have an effect type or if this is a PC note.
  702. // Never change the effect type.
  703. SetParamFromY(row, y);
  704. }
  705. break;
  706. }
  707. m_ModDoc.SetModified();
  708. m_ModDoc.UpdateAllViews(nullptr, PatternHint(m_nPattern).Data());
  709. }
  710. void CEffectVis::SetPcNote(ROWINDEX row)
  711. {
  712. if(!m_SndFile.Patterns.IsValidPat(m_nPattern))
  713. return;
  714. ModCommand &m = *m_SndFile.Patterns[m_nPattern].GetpModCommand(row, m_nChan);
  715. m.Set(m_templatePCNote.note, m_templatePCNote.instr, m_templatePCNote.GetValueVolCol(), 0);
  716. }
  717. bool CEffectVis::IsPcNote(ROWINDEX row) const
  718. {
  719. if(m_SndFile.Patterns.IsValidPat(m_nPattern))
  720. return m_SndFile.Patterns[m_nPattern].GetpModCommand(row, m_nChan)->IsPcNote();
  721. else
  722. return false;
  723. }
  724. OPENMPT_NAMESPACE_END