aboutsummaryrefslogtreecommitdiff
path: root/Src/external_dependencies/openmpt-trunk/mptrack/EffectVis.cpp
diff options
context:
space:
mode:
authorJef <jef@targetspot.com>2024-09-24 08:54:57 -0400
committerJef <jef@targetspot.com>2024-09-24 08:54:57 -0400
commit20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch)
tree12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/external_dependencies/openmpt-trunk/mptrack/EffectVis.cpp
parent537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff)
downloadwinamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz
Initial community commit
Diffstat (limited to 'Src/external_dependencies/openmpt-trunk/mptrack/EffectVis.cpp')
-rw-r--r--Src/external_dependencies/openmpt-trunk/mptrack/EffectVis.cpp873
1 files changed, 873 insertions, 0 deletions
diff --git a/Src/external_dependencies/openmpt-trunk/mptrack/EffectVis.cpp b/Src/external_dependencies/openmpt-trunk/mptrack/EffectVis.cpp
new file mode 100644
index 00000000..1a6a2e22
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/mptrack/EffectVis.cpp
@@ -0,0 +1,873 @@
+/*
+ * EffectVis.cpp
+ * -------------
+ * Purpose: Implementation of parameter visualisation dialog.
+ * Notes : (currenlty none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#include "stdafx.h"
+#include "Mptrack.h"
+#include "Mainfrm.h"
+#include "Childfrm.h"
+#include "Moddoc.h"
+#include "Globals.h"
+#include "View_pat.h"
+#include "EffectVis.h"
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+CEffectVis::EditAction CEffectVis::m_nAction = CEffectVis::kAction_OverwriteFX;
+
+IMPLEMENT_DYNAMIC(CEffectVis, CDialog)
+CEffectVis::CEffectVis(CViewPattern *pViewPattern, ROWINDEX startRow, ROWINDEX endRow, CHANNELINDEX nchn, CModDoc &modDoc, PATTERNINDEX pat)
+ : effectInfo(modDoc.GetSoundFile())
+ , m_ModDoc(modDoc)
+ , m_SndFile(modDoc.GetSoundFile())
+ , m_pViewPattern(pViewPattern)
+{
+ m_nFillEffect = effectInfo.GetIndexFromEffect(CMD_SMOOTHMIDI, 0);
+ m_templatePCNote.Set(NOTE_PCS, 1, 0, 0);
+ UpdateSelection(startRow, endRow, nchn, pat);
+}
+
+BEGIN_MESSAGE_MAP(CEffectVis, CDialog)
+ ON_WM_ERASEBKGND()
+ ON_WM_PAINT()
+ ON_WM_SIZE()
+ ON_WM_LBUTTONDOWN()
+ ON_WM_LBUTTONUP()
+ ON_WM_MOUSEMOVE()
+ ON_WM_RBUTTONDOWN()
+ ON_WM_RBUTTONUP()
+ ON_CBN_SELCHANGE(IDC_VISACTION, &CEffectVis::OnActionChanged)
+ ON_CBN_SELCHANGE(IDC_VISEFFECTLIST, &CEffectVis::OnEffectChanged)
+END_MESSAGE_MAP()
+
+void CEffectVis::DoDataExchange(CDataExchange* pDX)
+{
+ CDialog::DoDataExchange(pDX);
+ DDX_Control(pDX, IDC_VISSTATUS, m_edVisStatus);
+ DDX_Control(pDX, IDC_VISEFFECTLIST, m_cmbEffectList);
+ DDX_Control(pDX, IDC_VISACTION, m_cmbActionList);
+}
+
+void CEffectVis::OnActionChanged()
+{
+ m_nAction = static_cast<EditAction>(m_cmbActionList.GetItemData(m_cmbActionList.GetCurSel()));
+ if (m_nAction == kAction_FillPC
+ || m_nAction == kAction_OverwritePC
+ || m_nAction == kAction_Preserve)
+ m_cmbEffectList.EnableWindow(FALSE);
+ else
+ m_cmbEffectList.EnableWindow(TRUE);
+
+}
+
+void CEffectVis::OnEffectChanged()
+{
+ m_nFillEffect = static_cast<UINT>(m_cmbEffectList.GetItemData(m_cmbEffectList.GetCurSel()));
+}
+
+void CEffectVis::OnPaint()
+{
+ CPaintDC dc(this); // device context for painting
+ ShowVis(&dc);
+
+}
+
+uint16 CEffectVis::GetParam(ROWINDEX row) const
+{
+ uint16 paramValue = 0;
+
+ if(m_SndFile.Patterns.IsValidPat(m_nPattern))
+ {
+ const ModCommand &m = *m_SndFile.Patterns[m_nPattern].GetpModCommand(row, m_nChan);
+ if (m.IsPcNote())
+ {
+ paramValue = m.GetValueEffectCol();
+ } else
+ {
+ paramValue = m.param;
+ }
+ }
+
+ return paramValue;
+}
+
+// Sets a row's param value based on the vertical cursor position.
+// Sets either plain pattern effect parameter or PC note parameter
+// as appropriate, depending on contents of row.
+void CEffectVis::SetParamFromY(ROWINDEX row, int y)
+{
+ if(!m_SndFile.Patterns.IsValidPat(m_nPattern))
+ return;
+
+ ModCommand &m = *m_SndFile.Patterns[m_nPattern].GetpModCommand(row, m_nChan);
+ if (IsPcNote(row))
+ {
+ uint16 param = ScreenYToPCParam(y);
+ m.SetValueEffectCol(param);
+ } else
+ {
+ ModCommand::PARAM param = ScreenYToFXParam(y);
+ // Cap the parameter value as appropriate, based on effect type (e.g. Zxx gets capped to [0x00,0x7F])
+ effectInfo.GetEffectFromIndex(effectInfo.GetIndexFromEffect(m.command, param), param);
+ m.param = param;
+ }
+}
+
+
+EffectCommand CEffectVis::GetCommand(ROWINDEX row) const
+{
+ if(m_SndFile.Patterns.IsValidPat(m_nPattern))
+ return static_cast<EffectCommand>(m_SndFile.Patterns[m_nPattern].GetpModCommand(row, m_nChan)->command);
+ else
+ return CMD_NONE;
+}
+
+void CEffectVis::SetCommand(ROWINDEX row, EffectCommand command)
+{
+ if(m_SndFile.Patterns.IsValidPat(m_nPattern))
+ {
+ ModCommand &m = *m_SndFile.Patterns[m_nPattern].GetpModCommand(row, m_nChan);
+ if(m.IsPcNote())
+ {
+ // Clear PC note
+ m.note = 0;
+ m.instr = 0;
+ m.volcmd = VOLCMD_NONE;
+ m.vol = 0;
+ }
+ m.command = command;
+ }
+}
+
+int CEffectVis::RowToScreenX(ROWINDEX row) const
+{
+ if ((row >= m_startRow) || (row <= m_endRow))
+ return mpt::saturate_round<int>(m_rcDraw.left + m_innerBorder + (row - m_startRow) * m_pixelsPerRow);
+ return -1;
+}
+
+
+int CEffectVis::RowToScreenY(ROWINDEX row) const
+{
+ int screenY = -1;
+
+ if(m_SndFile.Patterns.IsValidPat(m_nPattern))
+ {
+ const ModCommand &m = *m_SndFile.Patterns[m_nPattern].GetpModCommand(row, m_nChan);
+ if (m.IsPcNote())
+ {
+ uint16 paramValue = m.GetValueEffectCol();
+ screenY = PCParamToScreenY(paramValue);
+ } else
+ {
+ uint16 paramValue = m.param;
+ screenY = FXParamToScreenY(paramValue);
+ }
+ }
+
+ return screenY;
+}
+
+int CEffectVis::FXParamToScreenY(uint16 param) const
+{
+ if(param >= 0x00 && param <= 0xFF)
+ return mpt::saturate_round<int>(m_rcDraw.bottom - param * m_pixelsPerFXParam);
+ return -1;
+}
+
+int CEffectVis::PCParamToScreenY(uint16 param) const
+{
+ if(param >= 0x00 && param <= ModCommand::maxColumnValue)
+ return mpt::saturate_round<int>(m_rcDraw.bottom - param*m_pixelsPerPCParam);
+ return -1;
+}
+
+ModCommand::PARAM CEffectVis::ScreenYToFXParam(int y) const
+{
+ if(y <= FXParamToScreenY(0xFF))
+ return 0xFF;
+
+ if(y >= FXParamToScreenY(0x00))
+ return 0x00;
+
+ return mpt::saturate_round<ModCommand::PARAM>((m_rcDraw.bottom - y) / m_pixelsPerFXParam);
+}
+
+uint16 CEffectVis::ScreenYToPCParam(int y) const
+{
+ if(y <= PCParamToScreenY(ModCommand::maxColumnValue))
+ return ModCommand::maxColumnValue;
+
+ if(y >= PCParamToScreenY(0x00))
+ return 0x00;
+
+ return mpt::saturate_round<uint16>((m_rcDraw.bottom - y) / m_pixelsPerPCParam);
+}
+
+ROWINDEX CEffectVis::ScreenXToRow(int x) const
+{
+ if(x <= RowToScreenX(m_startRow))
+ return m_startRow;
+
+ if(x >= RowToScreenX(m_endRow))
+ return m_endRow;
+
+ return mpt::saturate_round<ROWINDEX>(m_startRow + (x - m_innerBorder) / m_pixelsPerRow);
+}
+
+
+void CEffectVis::DrawGrid()
+{
+ // Lots of room for optimisation here.
+ // Draw vertical grid lines
+ ROWINDEX nBeat = m_SndFile.m_nDefaultRowsPerBeat, nMeasure = m_SndFile.m_nDefaultRowsPerMeasure;
+ if(m_SndFile.Patterns[m_nPattern].GetOverrideSignature())
+ {
+ nBeat = m_SndFile.Patterns[m_nPattern].GetRowsPerBeat();
+ nMeasure = m_SndFile.Patterns[m_nPattern].GetRowsPerMeasure();
+ }
+
+ m_dcGrid.FillSolidRect(&m_rcDraw, 0);
+ auto oldPen = m_dcGrid.SelectStockObject(DC_PEN);
+ for(ROWINDEX row = m_startRow; row <= m_endRow; row++)
+ {
+ if(row % nMeasure == 0)
+ m_dcGrid.SetDCPenColor(RGB(0xFF, 0xFF, 0xFF));
+ else if(row % nBeat == 0)
+ m_dcGrid.SetDCPenColor(RGB(0x99, 0x99, 0x99));
+ else
+ m_dcGrid.SetDCPenColor(RGB(0x55, 0x55, 0x55));
+ int x1 = RowToScreenX(row);
+ m_dcGrid.MoveTo(x1, m_rcDraw.top);
+ m_dcGrid.LineTo(x1, m_rcDraw.bottom);
+ }
+
+ // Draw horizontal grid lines
+ constexpr UINT numHorizontalLines = 4;
+ for(UINT i = 0; i < numHorizontalLines; i++)
+ {
+ COLORREF c = 0;
+ switch(i % 4)
+ {
+ case 0: c = RGB(0x00, 0x00, 0x00); break;
+ case 1: c = RGB(0x40, 0x40, 0x40); break;
+ case 2: c = RGB(0x80, 0x80, 0x80); break;
+ case 3: c = RGB(0xCC, 0xCC, 0xCC); break;
+ }
+ m_dcGrid.SetDCPenColor(c);
+ int y1 = m_rcDraw.bottom / numHorizontalLines * i;
+ m_dcGrid.MoveTo(m_rcDraw.left + m_innerBorder, y1);
+ m_dcGrid.LineTo(m_rcDraw.right - m_innerBorder, y1);
+ }
+ m_dcGrid.SelectObject(oldPen);
+}
+
+
+void CEffectVis::SetPlayCursor(PATTERNINDEX nPat, ROWINDEX nRow)
+{
+ if(nPat == m_nPattern && nRow == m_nOldPlayPos)
+ return;
+
+ if(m_nOldPlayPos >= m_startRow && m_nOldPlayPos <= m_endRow)
+ {
+ // erase current playpos
+ int x1 = RowToScreenX(m_nOldPlayPos);
+ m_dcPlayPos.SelectStockObject(BLACK_PEN);
+ m_dcPlayPos.MoveTo(x1,m_rcDraw.top);
+ m_dcPlayPos.LineTo(x1,m_rcDraw.bottom);
+ }
+
+ if((nRow < m_startRow) || (nRow > m_endRow) || (nPat != m_nPattern))
+ return;
+
+ int x1 = RowToScreenX(nRow);
+ m_dcPlayPos.SelectStockObject(DC_PEN);
+ m_dcPlayPos.SetDCPenColor(TrackerSettings::Instance().rgbCustomColors[MODCOLOR_SAMPLE]);
+ m_dcPlayPos.MoveTo(x1,m_rcDraw.top);
+ m_dcPlayPos.LineTo(x1,m_rcDraw.bottom);
+
+ m_nOldPlayPos = nRow;
+ InvalidateRect(NULL, FALSE);
+}
+
+
+void CEffectVis::ShowVis(CDC *pDC)
+{
+ if (m_forceRedraw)
+ {
+ m_forceRedraw = false;
+
+ // if we already have a memory dc, destroy it (this occurs for a re-size)
+ if (m_dcGrid.m_hDC)
+ {
+ m_dcGrid.SelectObject(m_pbOldGrid);
+ m_dcGrid.DeleteDC();
+
+ m_dcNodes.SelectObject(m_pbOldNodes);
+ m_dcNodes.DeleteDC();
+
+ m_dcPlayPos.SelectObject(m_pbOldPlayPos);
+ m_dcPlayPos.DeleteDC();
+
+ m_bPlayPos.DeleteObject();
+ m_bGrid.DeleteObject();
+ m_bNodes.DeleteObject();
+ }
+
+ // create a memory based dc for drawing the grid
+ m_dcGrid.CreateCompatibleDC(pDC);
+ m_bGrid.CreateCompatibleBitmap(pDC, m_rcDraw.Width(), m_rcDraw.Height());
+ m_pbOldGrid = *m_dcGrid.SelectObject(&m_bGrid);
+
+ // create a memory based dc for drawing the nodes
+ m_dcNodes.CreateCompatibleDC(pDC);
+ m_bNodes.CreateCompatibleBitmap(pDC, m_rcDraw.Width(), m_rcDraw.Height());
+ m_pbOldNodes = *m_dcNodes.SelectObject(&m_bNodes);
+
+ // create a memory based dc for drawing the nodes
+ m_dcPlayPos.CreateCompatibleDC(pDC);
+ m_bPlayPos.CreateCompatibleBitmap(pDC, m_rcDraw.Width(), m_rcDraw.Height());
+ m_pbOldPlayPos = *m_dcPlayPos.SelectObject(&m_bPlayPos);
+
+ SetPlayCursor(m_nPattern, m_nOldPlayPos);
+ DrawGrid();
+ DrawNodes();
+ }
+
+ // display the new image, combining the nodes with the grid
+ ShowVisImage(pDC);
+
+}
+
+
+void CEffectVis::ShowVisImage(CDC *pDC)
+{
+ // to avoid flicker, establish a memory dc, draw to it
+ // and then BitBlt it to the destination "pDC"
+ CDC memDC;
+ memDC.CreateCompatibleDC(pDC);
+ if (!memDC)
+ return;
+
+ CBitmap memBitmap;
+ memBitmap.CreateCompatibleBitmap(pDC, m_rcDraw.Width(), m_rcDraw.Height());
+ CBitmap *oldBitmap = memDC.SelectObject(&memBitmap);
+
+ // make sure we have the bitmaps
+ if (!m_dcGrid.m_hDC)
+ return;
+ if (!m_dcNodes.m_hDC)
+ return;
+ if (!m_dcPlayPos.m_hDC)
+ return;
+
+ if (memDC.m_hDC != nullptr)
+ {
+ // draw the grid
+ memDC.BitBlt(0, 0, m_rcDraw.Width(), m_rcDraw.Height(), &m_dcGrid, 0, 0, SRCCOPY);
+
+ // merge the nodes image with the grid
+ memDC.TransparentBlt(0, 0, m_rcDraw.Width(), m_rcDraw.Height(), &m_dcNodes, 0, 0, m_rcDraw.Width(), m_rcDraw.Height(), 0x00000000);
+ // further merge the playpos
+ memDC.TransparentBlt(0, 0, m_rcDraw.Width(), m_rcDraw.Height(), &m_dcPlayPos, 0, 0, m_rcDraw.Width(), m_rcDraw.Height(), 0x00000000);
+
+ // copy the resulting bitmap to the destination
+ pDC->BitBlt(0, 0, m_rcDraw.Width(), m_rcDraw.Height(), &memDC, 0, 0, SRCCOPY);
+ }
+
+ memDC.SelectObject(oldBitmap);
+
+}
+
+
+void CEffectVis::DrawNodes()
+{
+ if(m_rcDraw.IsRectEmpty())
+ return;
+
+ //Draw
+ const int lineWidth = Util::ScalePixels(1, m_hWnd);
+ const int nodeSizeHalf = m_nodeSizeHalf;
+ const int nodeSizeHalf2 = nodeSizeHalf - lineWidth + 1;
+ const int nodeSize = 2 * nodeSizeHalf + 1;
+
+ //erase
+ if ((ROWINDEX)m_nRowToErase < m_startRow || m_nParamToErase < 0)
+ {
+ m_dcNodes.FillSolidRect(&m_rcDraw, 0);
+ } else
+ {
+ int x = RowToScreenX(m_nRowToErase);
+ CRect r(x - nodeSizeHalf, m_rcDraw.top, x + nodeSizeHalf + 1, m_rcDraw.bottom);
+ m_dcNodes.FillSolidRect(&r, 0);
+ }
+
+ for (ROWINDEX row = m_startRow; row <= m_endRow; row++)
+ {
+ COLORREF col = IsPcNote(row) ? RGB(0xFF, 0xFF, 0x00) : RGB(0xD0, 0xFF, 0xFF);
+ int x = RowToScreenX(row);
+ int y = RowToScreenY(row);
+ m_dcNodes.FillSolidRect(x - nodeSizeHalf, y - nodeSizeHalf, nodeSize, lineWidth, col); // Top
+ m_dcNodes.FillSolidRect(x + nodeSizeHalf2, y - nodeSizeHalf, lineWidth, nodeSize, col); // Right
+ m_dcNodes.FillSolidRect(x - nodeSizeHalf, y + nodeSizeHalf2, nodeSize, lineWidth, col); // Bottom
+ m_dcNodes.FillSolidRect(x - nodeSizeHalf, y - nodeSizeHalf, lineWidth, nodeSize, col); // Left
+ }
+
+}
+
+void CEffectVis::InvalidateRow(int row)
+{
+ if (((UINT)row < m_startRow) || ((UINT)row > m_endRow)) return;
+
+//It seems this optimisation doesn't work properly yet. Disable in Update()
+
+ int x = RowToScreenX(row);
+ invalidated.bottom = m_rcDraw.bottom;
+ invalidated.top = m_rcDraw.top;
+ invalidated.left = x - m_nodeSizeHalf;
+ invalidated.right = x + m_nodeSizeHalf + 1;
+ InvalidateRect(&invalidated, FALSE);
+}
+
+
+void CEffectVis::OpenEditor(CWnd *parent)
+{
+ Create(IDD_EFFECTVISUALIZER, parent);
+ m_forceRedraw = true;
+
+ if(TrackerSettings::Instance().effectVisWidth > 0 && TrackerSettings::Instance().effectVisHeight > 0)
+ {
+ WINDOWPLACEMENT wnd;
+ wnd.length = sizeof(wnd);
+ GetWindowPlacement(&wnd);
+ wnd.showCmd = SW_SHOWNOACTIVATE;
+ CRect rect = wnd.rcNormalPosition;
+ if(TrackerSettings::Instance().effectVisX > int32_min && TrackerSettings::Instance().effectVisY > int32_min)
+ {
+ CRect mainRect;
+ CMainFrame::GetMainFrame()->GetWindowRect(mainRect);
+ rect.left = mainRect.left + MulDiv(TrackerSettings::Instance().effectVisX, Util::GetDPIx(m_hWnd), 96);
+ rect.top = mainRect.top + MulDiv(TrackerSettings::Instance().effectVisY, Util::GetDPIx(m_hWnd), 96);
+ }
+ rect.right = rect.left + MulDiv(TrackerSettings::Instance().effectVisWidth, Util::GetDPIx(m_hWnd), 96);
+ rect.bottom = rect.top + MulDiv(TrackerSettings::Instance().effectVisHeight, Util::GetDPIx(m_hWnd), 96);
+ wnd.rcNormalPosition = rect;
+ SetWindowPlacement(&wnd);
+ }
+
+ ShowWindow(SW_SHOW);
+}
+
+
+void CEffectVis::OnClose()
+{
+ DoClose();
+}
+
+
+void CEffectVis::OnOK()
+{
+ OnClose();
+}
+
+
+void CEffectVis::OnCancel()
+{
+ OnClose();
+}
+
+
+void CEffectVis::DoClose()
+{
+ WINDOWPLACEMENT wnd;
+ wnd.length = sizeof(wnd);
+ GetWindowPlacement(&wnd);
+ CRect mainRect;
+ CMainFrame::GetMainFrame()->GetWindowRect(mainRect);
+
+ CRect rect = wnd.rcNormalPosition;
+ rect.MoveToXY(rect.left - mainRect.left, rect.top - mainRect.top);
+ TrackerSettings::Instance().effectVisWidth = MulDiv(rect.Width(), 96, Util::GetDPIx(m_hWnd));
+ TrackerSettings::Instance().effectVisHeight = MulDiv(rect.Height(), 96, Util::GetDPIy(m_hWnd));
+ TrackerSettings::Instance().effectVisX = MulDiv(rect.left, 96, Util::GetDPIx(m_hWnd));
+ TrackerSettings::Instance().effectVisY = MulDiv(rect.top, 96, Util::GetDPIy(m_hWnd));
+
+ m_dcGrid.SelectObject(m_pbOldGrid);
+ m_dcGrid.DeleteDC();
+ m_dcNodes.SelectObject(m_pbOldNodes);
+ m_dcNodes.DeleteDC();
+ m_dcPlayPos.SelectObject(m_pbOldPlayPos);
+ m_dcPlayPos.DeleteDC();
+
+ m_bGrid.DeleteObject();
+ m_bNodes.DeleteObject();
+ m_bPlayPos.DeleteObject();
+
+ DestroyWindow();
+}
+
+
+void CEffectVis::PostNcDestroy()
+{
+ m_pViewPattern->m_pEffectVis = nullptr;
+}
+
+
+void CEffectVis::OnSize(UINT nType, int cx, int cy)
+{
+ MPT_UNREFERENCED_PARAMETER(nType);
+ MPT_UNREFERENCED_PARAMETER(cx);
+ MPT_UNREFERENCED_PARAMETER(cy);
+ GetClientRect(&m_rcFullWin);
+ m_rcDraw.SetRect(m_rcFullWin.left, m_rcFullWin.top, m_rcFullWin.right, m_rcFullWin.bottom - m_marginBottom);
+
+ const int actionListWidth = Util::ScalePixels(170, m_hWnd);
+ const int commandListWidth = Util::ScalePixels(160, m_hWnd);
+
+ if (IsWindow(m_edVisStatus.m_hWnd))
+ 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);
+ if (IsWindow(m_cmbActionList))
+ 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);
+ if (IsWindow(m_cmbEffectList))
+ 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);
+
+ if(m_nRows)
+ m_pixelsPerRow = (float)(m_rcDraw.Width() - m_innerBorder * 2) / (float)m_nRows;
+ else
+ m_pixelsPerRow = 1;
+ m_pixelsPerFXParam = (float)(m_rcDraw.Height())/(float)0xFF;
+ m_pixelsPerPCParam = (float)(m_rcDraw.Height())/(float)ModCommand::maxColumnValue;
+ m_forceRedraw = true;
+ InvalidateRect(NULL, FALSE); //redraw everything
+}
+
+void CEffectVis::Update()
+{
+ DrawNodes();
+ if (::IsWindow(m_hWnd))
+ {
+ OnPaint();
+ if (m_nRowToErase<0)
+ InvalidateRect(NULL, FALSE); // redraw everything
+ else
+ {
+ InvalidateRow(m_nRowToErase);
+ m_nParamToErase=-1;
+ m_nRowToErase=-1;
+ }
+
+ }
+}
+
+void CEffectVis::UpdateSelection(ROWINDEX startRow, ROWINDEX endRow, CHANNELINDEX nchn, PATTERNINDEX pat)
+{
+ m_startRow = startRow;
+ m_endRow = endRow;
+ m_nRows = endRow - startRow;
+ m_nChan = nchn;
+ m_nPattern = pat;
+
+ //Check pattern, start row and channel exist
+ if(!m_SndFile.Patterns.IsValidPat(m_nPattern) || !m_SndFile.Patterns[m_nPattern].IsValidRow(m_startRow) || m_nChan >= m_SndFile.GetNumChannels())
+ {
+ DoClose();
+ return;
+ }
+
+ //Check end exists
+ if(!m_SndFile.Patterns[m_nPattern].IsValidRow(m_endRow))
+ {
+ m_endRow = m_SndFile.Patterns[m_nPattern].GetNumRows() - 1;
+ }
+
+ if(m_nRows)
+ m_pixelsPerRow = (float)(m_rcDraw.Width() - m_innerBorder * 2) / (float)m_nRows;
+ else
+ m_pixelsPerRow = 1;
+ m_pixelsPerFXParam = (float)(m_rcDraw.Height())/(float)0xFF;
+ m_pixelsPerPCParam = (float)(m_rcDraw.Height())/(float)ModCommand::maxColumnValue;
+
+ m_forceRedraw = true;
+ Update();
+
+}
+
+void CEffectVis::OnRButtonDown(UINT nFlags, CPoint point)
+{
+ if (!(m_dwStatus & FXVSTATUS_LDRAGGING))
+ {
+ SetFocus();
+ SetCapture();
+
+ m_nDragItem = ScreenXToRow(point.x);
+ m_dwStatus |= FXVSTATUS_RDRAGGING;
+ m_ModDoc.GetPatternUndo().PrepareUndo(static_cast<PATTERNINDEX>(m_nPattern), m_nChan, m_nDragItem, 1, 1, "Parameter Editor entry");
+ OnMouseMove(nFlags, point);
+ }
+
+ CDialog::OnRButtonDown(nFlags, point);
+}
+
+void CEffectVis::OnRButtonUp(UINT nFlags, CPoint point)
+{
+ ReleaseCapture();
+ m_dwStatus = 0x00;
+ m_nDragItem = -1;
+ CDialog::OnRButtonUp(nFlags, point);
+}
+
+void CEffectVis::OnMouseMove(UINT nFlags, CPoint point)
+{
+ CDialog::OnMouseMove(nFlags, point);
+
+ ROWINDEX row = ScreenXToRow(point.x);
+
+ if ((m_dwStatus & FXVSTATUS_RDRAGGING) && (m_nDragItem>=0) )
+ {
+ m_nRowToErase = m_nDragItem;
+ m_nParamToErase = GetParam(m_nDragItem);
+
+ MakeChange(m_nDragItem, point.y);
+ } else if ((m_dwStatus & FXVSTATUS_LDRAGGING))
+ {
+ // Interpolate if we detect that rows have been skipped but the left mouse button was not released.
+ // 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)
+ const int steps = std::abs((int)row - (int)m_nLastDrawnRow);
+ if (m_nLastDrawnRow != ROWINDEX_INVALID && m_nLastDrawnRow > m_startRow && steps > 1)
+ {
+ int direction = ((int)(row - m_nLastDrawnRow) > 0) ? 1 : -1;
+ float factor = (float)(point.y - m_nLastDrawnY)/(float)steps + 0.5f;
+
+ int currentRow;
+ for (int i=1; i<=steps; i++)
+ {
+ currentRow = m_nLastDrawnRow+(direction*i);
+ int interpolatedY = mpt::saturate_round<int>(m_nLastDrawnY + ((float)i * factor));
+ MakeChange(currentRow, interpolatedY);
+ }
+
+ //Don't use single value update
+ m_nRowToErase = -1;
+ m_nParamToErase = -1;
+ } else
+ {
+ m_nRowToErase = -1;
+ m_nParamToErase = -1;
+ MakeChange(row, point.y);
+ }
+
+ // Remember last modified point in case we need to interpolate
+ m_nLastDrawnRow = row;
+ m_nLastDrawnY = point.y;
+ }
+ //update status bar
+ CString status;
+ CString effectName;
+ uint16 paramValue;
+
+
+ if (IsPcNote(row))
+ {
+ paramValue = ScreenYToPCParam(point.y);
+ effectName.Format(_T("%s"), _T("Param Control")); // TODO - show smooth & plug+param
+ } else
+ {
+ paramValue = ScreenYToFXParam(point.y);
+ effectInfo.GetEffectInfo(effectInfo.GetIndexFromEffect(GetCommand(row), ModCommand::PARAM(GetParam(row))), &effectName, true);
+ }
+
+ status.Format(_T("Pat: %d\tChn: %d\tRow: %d\tVal: %02X (%03d) [%s]"),
+ m_nPattern, m_nChan+1, static_cast<signed int>(row), paramValue, paramValue, effectName.GetString());
+ m_edVisStatus.SetWindowText(status);
+}
+
+void CEffectVis::OnLButtonDown(UINT nFlags, CPoint point)
+{
+ if (!(m_dwStatus & FXVSTATUS_RDRAGGING))
+ {
+ SetFocus();
+ SetCapture();
+
+ m_nDragItem = ScreenXToRow(point.x);
+ m_dwStatus |= FXVSTATUS_LDRAGGING;
+ m_ModDoc.GetPatternUndo().PrepareUndo(static_cast<PATTERNINDEX>(m_nPattern), m_nChan, m_startRow, 1, m_endRow - m_startRow + 1, "Parameter Editor entry");
+ OnMouseMove(nFlags, point);
+ }
+
+ CDialog::OnLButtonDown(nFlags, point);
+}
+
+void CEffectVis::OnLButtonUp(UINT nFlags, CPoint point)
+{
+ ReleaseCapture();
+ m_dwStatus = 0x00;
+ CDialog::OnLButtonUp(nFlags, point);
+ m_nLastDrawnRow = ROWINDEX_INVALID;
+}
+
+
+BOOL CEffectVis::OnInitDialog()
+{
+ CDialog::OnInitDialog();
+
+ int dpi = Util::GetDPIx(m_hWnd);
+ m_nodeSizeHalf = MulDiv(3, dpi, 96);
+ m_marginBottom = MulDiv(20, dpi, 96);
+ m_innerBorder = MulDiv(4, dpi, 96);
+
+ // 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
+ // and use it as a template for new PC notes that will be created via the visualiser.
+ bool isPCevent = IsPcNote(m_startRow);
+ if(!isPCevent)
+ {
+ for(ROWINDEX row = m_startRow; row <= m_endRow; row++)
+ {
+ if(IsPcNote(row))
+ {
+ isPCevent = true;
+ } else if(GetCommand(row) != CMD_NONE)
+ {
+ isPCevent = false;
+ break;
+ }
+ }
+ }
+
+ if(m_ModDoc.GetModType() == MOD_TYPE_MPT && isPCevent)
+ {
+ m_nAction = kAction_OverwritePC;
+ if(m_SndFile.Patterns.IsValidPat(m_nPattern))
+ {
+ ModCommand &m = *m_SndFile.Patterns[m_nPattern].GetpModCommand(m_startRow, m_nChan);
+ m_templatePCNote.Set(m.note, m.instr, m.GetValueVolCol(), 0);
+ }
+ m_cmbEffectList.EnableWindow(FALSE);
+ } else
+ {
+ // Otherwise, default to FX overwrite and
+ // use effect of first selected row as default effect type
+ m_nAction = kAction_OverwriteFX;
+ m_nFillEffect = effectInfo.GetIndexFromEffect(GetCommand(m_startRow), ModCommand::PARAM(GetParam(m_startRow)));
+ if (m_nFillEffect < 0 || m_nFillEffect >= MAX_EFFECTS)
+ m_nFillEffect = effectInfo.GetIndexFromEffect(CMD_SMOOTHMIDI, 0);
+ }
+
+
+ CString s;
+ UINT numfx = effectInfo.GetNumEffects();
+ m_cmbEffectList.ResetContent();
+ int k;
+ for (UINT i=0; i<numfx; i++)
+ {
+ if (effectInfo.GetEffectInfo(i, &s, true))
+ {
+ k =m_cmbEffectList.AddString(s);
+ m_cmbEffectList.SetItemData(k, i);
+ if ((int)i == m_nFillEffect)
+ m_cmbEffectList.SetCurSel(k);
+ }
+ }
+
+ m_cmbActionList.ResetContent();
+ m_cmbActionList.SetItemData(m_cmbActionList.AddString(_T("Overwrite with effect:")), kAction_OverwriteFX);
+ m_cmbActionList.SetItemData(m_cmbActionList.AddString(_T("Overwrite effect next to note:")), kAction_OverwriteFXWithNote);
+ m_cmbActionList.SetItemData(m_cmbActionList.AddString(_T("Fill blanks with effect:")), kAction_FillFX);
+ if (m_ModDoc.GetModType() == MOD_TYPE_MPT)
+ {
+ m_cmbActionList.SetItemData(m_cmbActionList.AddString(_T("Overwrite with PC note")), kAction_OverwritePC);
+ m_cmbActionList.SetItemData(m_cmbActionList.AddString(_T("Fill blanks with PC note")), kAction_FillPC);
+ }
+ m_cmbActionList.SetItemData(m_cmbActionList.AddString(_T("Never change effect type")), kAction_Preserve);
+
+ m_cmbActionList.SetCurSel(m_nAction);
+ return true;
+}
+
+void CEffectVis::MakeChange(ROWINDEX row, int y)
+{
+ if(!m_SndFile.Patterns.IsValidPat(m_nPattern))
+ return;
+
+ ModCommand &m = *m_SndFile.Patterns[m_nPattern].GetpModCommand(row, m_nChan);
+
+ switch (m_nAction)
+ {
+ case kAction_FillFX:
+ // Only set command if there isn't a command already at this row and it's not a PC note
+ if (GetCommand(row) == CMD_NONE && !IsPcNote(row))
+ {
+ SetCommand(row, effectInfo.GetEffectFromIndex(m_nFillEffect));
+ }
+ // Always set param
+ SetParamFromY(row, y);
+ break;
+
+ case kAction_OverwriteFXWithNote:
+ if(!m_SndFile.Patterns.IsValidPat(m_nPattern))
+ break;
+ if(!m_SndFile.Patterns[m_nPattern].GetpModCommand(row, m_nChan)->IsNote())
+ break;
+ [[fallthrough]];
+ case kAction_OverwriteFX:
+ // Always set command and param. Blows away any PC notes.
+ SetCommand(row, effectInfo.GetEffectFromIndex(m_nFillEffect));
+ SetParamFromY(row, y);
+ break;
+
+ case kAction_FillPC:
+ // Fill only empty slots with PC notes - leave other slots alone.
+ if (m.IsEmpty())
+ {
+ SetPcNote(row);
+ }
+ // Always set param
+ SetParamFromY(row, y);
+ break;
+
+ case kAction_OverwritePC:
+ // Always convert to PC Note and set param value
+ SetPcNote(row);
+ SetParamFromY(row, y);
+ break;
+
+ case kAction_Preserve:
+ if (GetCommand(row) != CMD_NONE || IsPcNote(row))
+ {
+ // Only set param if we have an effect type or if this is a PC note.
+ // Never change the effect type.
+ SetParamFromY(row, y);
+ }
+ break;
+
+ }
+
+ m_ModDoc.SetModified();
+ m_ModDoc.UpdateAllViews(nullptr, PatternHint(m_nPattern).Data());
+}
+
+void CEffectVis::SetPcNote(ROWINDEX row)
+{
+ if(!m_SndFile.Patterns.IsValidPat(m_nPattern))
+ return;
+
+ ModCommand &m = *m_SndFile.Patterns[m_nPattern].GetpModCommand(row, m_nChan);
+ m.Set(m_templatePCNote.note, m_templatePCNote.instr, m_templatePCNote.GetValueVolCol(), 0);
+}
+
+bool CEffectVis::IsPcNote(ROWINDEX row) const
+{
+ if(m_SndFile.Patterns.IsValidPat(m_nPattern))
+ return m_SndFile.Patterns[m_nPattern].GetpModCommand(row, m_nChan)->IsPcNote();
+ else
+ return false;
+}
+
+
+OPENMPT_NAMESPACE_END