diff options
author | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
---|---|---|
committer | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
commit | 20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch) | |
tree | 12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/external_dependencies/openmpt-trunk/mptrack/EffectVis.cpp | |
parent | 537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff) | |
download | winamp-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.cpp | 873 |
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 |