/* * 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(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(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(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(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(m_rcDraw.bottom - param * m_pixelsPerFXParam); return -1; } int CEffectVis::PCParamToScreenY(uint16 param) const { if(param >= 0x00 && param <= ModCommand::maxColumnValue) return mpt::saturate_round(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((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((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(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(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(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(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(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; iIsNote()) 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