/* * view_ins.cpp * ------------ * Purpose: Instrument tab, lower panel. * Notes : (currently none) * Authors: Olivier Lapicque * 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 "InputHandler.h" #include "ImageLists.h" #include "Childfrm.h" #include "Moddoc.h" #include "Globals.h" #include "Ctrl_ins.h" #include "View_ins.h" #include "Dlsbank.h" #include "ChannelManagerDlg.h" #include "ScaleEnvPointsDlg.h" #include "../soundlib/MIDIEvents.h" #include "../soundlib/mod_specifications.h" #include "../common/mptStringBuffer.h" #include "FileDialog.h" OPENMPT_NAMESPACE_BEGIN namespace { const int ENV_POINT_SIZE = 4; const float ENV_MIN_ZOOM = 2.0f; const float ENV_MAX_ZOOM = 256.0f; } // Non-client toolbar #define ENV_LEFTBAR_CY Util::ScalePixels(29, m_hWnd) #define ENV_LEFTBAR_CXSEP Util::ScalePixels(14, m_hWnd) #define ENV_LEFTBAR_CXSPC Util::ScalePixels(3, m_hWnd) #define ENV_LEFTBAR_CXBTN Util::ScalePixels(24, m_hWnd) #define ENV_LEFTBAR_CYBTN Util::ScalePixels(22, m_hWnd) static constexpr UINT cLeftBarButtons[ENV_LEFTBAR_BUTTONS] = { ID_ENVSEL_VOLUME, ID_ENVSEL_PANNING, ID_ENVSEL_PITCH, ID_SEPARATOR, ID_ENVELOPE_VOLUME, ID_ENVELOPE_PANNING, ID_ENVELOPE_PITCH, ID_ENVELOPE_FILTER, ID_SEPARATOR, ID_ENVELOPE_SETLOOP, ID_ENVELOPE_SUSTAIN, ID_ENVELOPE_CARRY, ID_SEPARATOR, ID_INSTRUMENT_SAMPLEMAP, ID_SEPARATOR, ID_ENVELOPE_VIEWGRID, ID_SEPARATOR, ID_ENVELOPE_ZOOM_IN, ID_ENVELOPE_ZOOM_OUT, ID_SEPARATOR, ID_ENVELOPE_LOAD, ID_ENVELOPE_SAVE, }; IMPLEMENT_SERIAL(CViewInstrument, CModScrollView, 0) BEGIN_MESSAGE_MAP(CViewInstrument, CModScrollView) //{{AFX_MSG_MAP(CViewInstrument) #if !defined(MPT_BUILD_RETRO) ON_MESSAGE(WM_DPICHANGED, &CViewInstrument::OnDPIChanged) #endif ON_WM_ERASEBKGND() ON_WM_SETFOCUS() ON_WM_SIZE() ON_WM_NCCALCSIZE() ON_WM_NCPAINT() ON_WM_NCHITTEST() ON_WM_MOUSEMOVE() ON_WM_NCMOUSEMOVE() ON_WM_LBUTTONDOWN() ON_WM_LBUTTONUP() ON_WM_LBUTTONDBLCLK() ON_WM_RBUTTONDOWN() ON_WM_MBUTTONDOWN() ON_WM_XBUTTONUP() ON_WM_NCLBUTTONDOWN() ON_WM_NCLBUTTONUP() ON_WM_NCLBUTTONDBLCLK() ON_WM_DROPFILES() ON_COMMAND(ID_PREVINSTRUMENT, &CViewInstrument::OnPrevInstrument) ON_COMMAND(ID_NEXTINSTRUMENT, &CViewInstrument::OnNextInstrument) ON_COMMAND(ID_ENVELOPE_SETLOOP, &CViewInstrument::OnEnvLoopChanged) ON_COMMAND(ID_ENVELOPE_SUSTAIN, &CViewInstrument::OnEnvSustainChanged) ON_COMMAND(ID_ENVELOPE_CARRY, &CViewInstrument::OnEnvCarryChanged) ON_COMMAND(ID_ENVELOPE_INSERTPOINT, &CViewInstrument::OnEnvInsertPoint) ON_COMMAND(ID_ENVELOPE_REMOVEPOINT, &CViewInstrument::OnEnvRemovePoint) ON_COMMAND(ID_ENVELOPE_VOLUME, &CViewInstrument::OnEnvVolChanged) ON_COMMAND(ID_ENVELOPE_PANNING, &CViewInstrument::OnEnvPanChanged) ON_COMMAND(ID_ENVELOPE_PITCH, &CViewInstrument::OnEnvPitchChanged) ON_COMMAND(ID_ENVELOPE_FILTER, &CViewInstrument::OnEnvFilterChanged) ON_COMMAND(ID_ENVELOPE_VIEWGRID, &CViewInstrument::OnEnvToggleGrid) ON_COMMAND(ID_ENVELOPE_ZOOM_IN, &CViewInstrument::OnEnvZoomIn) ON_COMMAND(ID_ENVELOPE_ZOOM_OUT, &CViewInstrument::OnEnvZoomOut) ON_COMMAND(ID_ENVELOPE_LOAD, &CViewInstrument::OnEnvLoad) ON_COMMAND(ID_ENVELOPE_SAVE, &CViewInstrument::OnEnvSave) ON_COMMAND(ID_ENVSEL_VOLUME, &CViewInstrument::OnSelectVolumeEnv) ON_COMMAND(ID_ENVSEL_PANNING, &CViewInstrument::OnSelectPanningEnv) ON_COMMAND(ID_ENVSEL_PITCH, &CViewInstrument::OnSelectPitchEnv) ON_COMMAND(ID_EDIT_COPY, &CViewInstrument::OnEditCopy) ON_COMMAND(ID_EDIT_PASTE, &CViewInstrument::OnEditPaste) ON_COMMAND(ID_EDIT_UNDO, &CViewInstrument::OnEditUndo) ON_COMMAND(ID_EDIT_REDO, &CViewInstrument::OnEditRedo) ON_COMMAND(ID_INSTRUMENT_SAMPLEMAP, &CViewInstrument::OnEditSampleMap) ON_COMMAND(ID_ENVELOPE_TOGGLERELEASENODE, &CViewInstrument::OnEnvToggleReleasNode) ON_COMMAND(ID_ENVELOPE_SCALEPOINTS, &CViewInstrument::OnEnvelopeScalePoints) ON_MESSAGE(WM_MOD_MIDIMSG, &CViewInstrument::OnMidiMsg) ON_MESSAGE(WM_MOD_KEYCOMMAND, &CViewInstrument::OnCustomKeyMsg) ON_UPDATE_COMMAND_UI(ID_EDIT_UNDO, &CViewInstrument::OnUpdateUndo) ON_UPDATE_COMMAND_UI(ID_EDIT_REDO, &CViewInstrument::OnUpdateRedo) //}}AFX_MSG_MAP ON_WM_MOUSEWHEEL() END_MESSAGE_MAP() /////////////////////////////////////////////////////////////// // CViewInstrument operations CViewInstrument::CViewInstrument() { EnableActiveAccessibility(); m_rcClient.bottom = 2; m_dwNotifyPos.fill(uint32(Notification::PosInvalid)); MemsetZero(m_NcButtonState); m_bmpEnvBar.Create(&CMainFrame::GetMainFrame()->m_EnvelopeIcons); m_baPlayingNote.reset(); } void CViewInstrument::OnInitialUpdate() { CModScrollView::OnInitialUpdate(); ModifyStyleEx(0, WS_EX_ACCEPTFILES); m_zoom = (ENV_POINT_SIZE * m_nDPIx) / 96.0f; m_envPointSize = Util::ScalePixels(ENV_POINT_SIZE, m_hWnd); UpdateScrollSize(); UpdateNcButtonState(); EnableToolTips(); } void CViewInstrument::UpdateScrollSize() { CModDoc *pModDoc = GetDocument(); GetClientRect(&m_rcClient); if(m_rcClient.bottom < 2) m_rcClient.bottom = 2; if(pModDoc) { SIZE sizeTotal, sizePage, sizeLine; uint32 maxTick = EnvGetTick(EnvGetLastPoint()); sizeTotal.cx = mpt::saturate_round((maxTick + 2) * m_zoom); sizeTotal.cy = 1; sizeLine.cx = mpt::saturate_round(m_zoom); sizeLine.cy = 2; sizePage.cx = sizeLine.cx * 4; sizePage.cy = sizeLine.cy; SetScrollSizes(MM_TEXT, sizeTotal, sizePage, sizeLine); } } LRESULT CViewInstrument::OnDPIChanged(WPARAM wParam, LPARAM lParam) { LRESULT res = CModScrollView::OnDPIChanged(wParam, lParam); m_envPointSize = Util::ScalePixels(4, m_hWnd); return res; } void CViewInstrument::PrepareUndo(const char *description) { GetDocument()->GetInstrumentUndo().PrepareUndo(m_nInstrument, description, m_nEnv); } // Set instrument (and moddoc) as modified. // updateAll: Update all views including this one. Otherwise, only update update other views. void CViewInstrument::SetModified(InstrumentHint hint, bool updateAll) { CModDoc *pModDoc = GetDocument(); pModDoc->SetModified(); pModDoc->UpdateAllViews(nullptr, hint.SetData(m_nInstrument), updateAll ? nullptr : this); CMainFrame::GetMainFrame()->NotifyAccessibilityUpdate(*this); } BOOL CViewInstrument::SetCurrentInstrument(INSTRUMENTINDEX nIns, EnvelopeType nEnv) { CModDoc *pModDoc = GetDocument(); Notification::Type type; if((!pModDoc) || (nIns < 1) || (nIns >= MAX_INSTRUMENTS)) return FALSE; m_nEnv = nEnv; m_nInstrument = nIns; switch(m_nEnv) { case ENV_PANNING: type = Notification::PanEnv; break; case ENV_PITCH: type = Notification::PitchEnv; break; default: m_nEnv = ENV_VOLUME; type = Notification::VolEnv; break; } pModDoc->SetNotifications(type, m_nInstrument); pModDoc->SetFollowWnd(m_hWnd); UpdateScrollSize(); UpdateNcButtonState(); InvalidateRect(NULL, FALSE); return TRUE; } void CViewInstrument::OnSetFocus(CWnd *pOldWnd) { CScrollView::OnSetFocus(pOldWnd); SetCurrentInstrument(m_nInstrument, m_nEnv); } LRESULT CViewInstrument::OnModViewMsg(WPARAM wParam, LPARAM lParam) { switch(wParam) { case VIEWMSG_SETCURRENTINSTRUMENT: SetCurrentInstrument(lParam & 0xFFFF, m_nEnv); break; case VIEWMSG_LOADSTATE: if(lParam) { INSTRUMENTVIEWSTATE *pState = (INSTRUMENTVIEWSTATE *)lParam; if(pState->initialized) { m_zoom = pState->zoom; SetCurrentInstrument(m_nInstrument, pState->nEnv); m_bGrid = pState->bGrid; } } break; case VIEWMSG_SAVESTATE: if(lParam) { INSTRUMENTVIEWSTATE *pState = (INSTRUMENTVIEWSTATE *)lParam; pState->initialized = true; pState->zoom = m_zoom; pState->nEnv = m_nEnv; pState->bGrid = m_bGrid; } break; default: return CModScrollView::OnModViewMsg(wParam, lParam); } return 0; } uint32 CViewInstrument::EnvGetTick(int nPoint) const { InstrumentEnvelope *envelope = GetEnvelopePtr(); if(envelope == nullptr) return 0; if((nPoint >= 0) && (nPoint < (int)envelope->size())) return envelope->at(nPoint).tick; else return 0; } uint32 CViewInstrument::EnvGetValue(int nPoint) const { InstrumentEnvelope *envelope = GetEnvelopePtr(); if(envelope == nullptr) return 0; if(nPoint >= 0 && nPoint < (int)envelope->size()) return envelope->at(nPoint).value; else return 0; } bool CViewInstrument::EnvSetValue(int nPoint, int32 nTick, int32 nValue, bool moveTail) { InstrumentEnvelope *envelope = GetEnvelopePtr(); if(envelope == nullptr || nPoint < 0) return false; if(nPoint == 0) { nTick = 0; moveTail = false; } int tickDiff = 0; bool ok = false; if(nPoint < (int)envelope->size()) { if(nTick != int32_min) { nTick = std::max(0, nTick); tickDiff = envelope->at(nPoint).tick; int mintick = (nPoint > 0) ? envelope->at(nPoint - 1).tick : 0; int maxtick; if(nPoint + 1 >= (int)envelope->size() || moveTail) maxtick = std::numeric_limits::max(); else maxtick = envelope->at(nPoint + 1).tick; // Can't have multiple points on same tick if(nPoint > 0 && mintick < maxtick - 1) { mintick++; if(nPoint + 1 < (int)envelope->size()) maxtick--; } if(nTick < mintick) nTick = mintick; if(nTick > maxtick) nTick = maxtick; if(nTick != envelope->at(nPoint).tick) { envelope->at(nPoint).tick = static_cast(nTick); ok = true; } } const int maxVal = (GetDocument()->GetModType() != MOD_TYPE_XM || m_nEnv != ENV_PANNING) ? 64 : 63; if(nValue != int32_min) { Limit(nValue, 0, maxVal); if(nValue != envelope->at(nPoint).value) { envelope->at(nPoint).value = static_cast(nValue); ok = true; } } } if(ok && moveTail) { // Move all points after modified point as well. tickDiff = envelope->at(nPoint).tick - tickDiff; for(auto it = envelope->begin() + nPoint + 1; it != envelope->end(); it++) { it->tick = static_cast(std::max(0, (int)it->tick + tickDiff)); } } return ok; } uint32 CViewInstrument::EnvGetNumPoints() const { InstrumentEnvelope *envelope = GetEnvelopePtr(); if(envelope == nullptr) return 0; return envelope->size(); } uint32 CViewInstrument::EnvGetLastPoint() const { uint32 nPoints = EnvGetNumPoints(); if(nPoints > 0) return nPoints - 1; return 0; } // Return if an envelope flag is set. bool CViewInstrument::EnvGetFlag(const EnvelopeFlags dwFlag) const { InstrumentEnvelope *pEnv = GetEnvelopePtr(); if(pEnv != nullptr) return pEnv->dwFlags[dwFlag]; return false; } uint32 CViewInstrument::EnvGetLoopStart() const { InstrumentEnvelope *envelope = GetEnvelopePtr(); if(envelope == nullptr) return 0; return envelope->nLoopStart; } uint32 CViewInstrument::EnvGetLoopEnd() const { InstrumentEnvelope *envelope = GetEnvelopePtr(); if(envelope == nullptr) return 0; return envelope->nLoopEnd; } uint32 CViewInstrument::EnvGetSustainStart() const { InstrumentEnvelope *envelope = GetEnvelopePtr(); if(envelope == nullptr) return 0; return envelope->nSustainStart; } uint32 CViewInstrument::EnvGetSustainEnd() const { InstrumentEnvelope *envelope = GetEnvelopePtr(); if(envelope == nullptr) return 0; return envelope->nSustainEnd; } bool CViewInstrument::EnvGetVolEnv() const { ModInstrument *pIns = GetInstrumentPtr(); if(pIns) return pIns->VolEnv.dwFlags[ENV_ENABLED] != 0; return false; } bool CViewInstrument::EnvGetPanEnv() const { ModInstrument *pIns = GetInstrumentPtr(); if(pIns) return pIns->PanEnv.dwFlags[ENV_ENABLED] != 0; return false; } bool CViewInstrument::EnvGetPitchEnv() const { ModInstrument *pIns = GetInstrumentPtr(); if(pIns) return ((pIns->PitchEnv.dwFlags & (ENV_ENABLED | ENV_FILTER)) == ENV_ENABLED); return false; } bool CViewInstrument::EnvGetFilterEnv() const { ModInstrument *pIns = GetInstrumentPtr(); if(pIns) return ((pIns->PitchEnv.dwFlags & (ENV_ENABLED | ENV_FILTER)) == (ENV_ENABLED | ENV_FILTER)); return false; } bool CViewInstrument::EnvSetLoopStart(int nPoint) { InstrumentEnvelope *envelope = GetEnvelopePtr(); if(envelope == nullptr) return false; if(nPoint < 0 || nPoint > (int)EnvGetLastPoint()) return false; if(nPoint != envelope->nLoopStart) { envelope->nLoopStart = static_castnLoopStart)>(nPoint); if(envelope->nLoopEnd < nPoint) envelope->nLoopEnd = static_castnLoopEnd)>(nPoint); return true; } else { return false; } } bool CViewInstrument::EnvSetLoopEnd(int nPoint) { InstrumentEnvelope *envelope = GetEnvelopePtr(); if(envelope == nullptr) return false; if(nPoint < 0 || nPoint > (int)EnvGetLastPoint()) return false; if(nPoint != envelope->nLoopEnd) { envelope->nLoopEnd = static_castnLoopEnd)>(nPoint); if(envelope->nLoopStart > nPoint) envelope->nLoopStart = static_castnLoopStart)>(nPoint); return true; } else { return false; } } bool CViewInstrument::EnvSetSustainStart(int nPoint) { InstrumentEnvelope *envelope = GetEnvelopePtr(); if(envelope == nullptr) return false; if(nPoint < 0 || nPoint > (int)EnvGetLastPoint()) return false; // We won't do any security checks here as GetEnvelopePtr() does that for us. CSoundFile &sndFile = GetDocument()->GetSoundFile(); if(nPoint != envelope->nSustainStart) { envelope->nSustainStart = static_castnSustainStart)>(nPoint); if((envelope->nSustainEnd < nPoint) || (sndFile.GetType() & MOD_TYPE_XM)) envelope->nSustainEnd = static_castnSustainEnd)>(nPoint); return true; } else { return false; } } bool CViewInstrument::EnvSetSustainEnd(int nPoint) { InstrumentEnvelope *envelope = GetEnvelopePtr(); if(envelope == nullptr) return false; if(nPoint < 0 || nPoint > (int)EnvGetLastPoint()) return false; // We won't do any security checks here as GetEnvelopePtr() does that for us. CSoundFile &sndFile = GetDocument()->GetSoundFile(); if(nPoint != envelope->nSustainEnd) { envelope->nSustainEnd = static_castnSustainEnd)>(nPoint); if((envelope->nSustainStart > nPoint) || (sndFile.GetType() & MOD_TYPE_XM)) envelope->nSustainStart = static_castnSustainStart)>(nPoint); return true; } else { return false; } } bool CViewInstrument::EnvToggleReleaseNode(int nPoint) { InstrumentEnvelope *envelope = GetEnvelopePtr(); if(envelope == nullptr) return false; if(nPoint < 0 || nPoint >= (int)EnvGetNumPoints()) return false; // Don't allow release nodes in IT/XM. GetDocument()/... nullptr check is done in GetEnvelopePtr, so no need to check twice. if(!GetDocument()->GetSoundFile().GetModSpecifications().hasReleaseNode) { if(envelope->nReleaseNode != ENV_RELEASE_NODE_UNSET) { envelope->nReleaseNode = ENV_RELEASE_NODE_UNSET; return true; } return false; } if(envelope->nReleaseNode == nPoint) { envelope->nReleaseNode = ENV_RELEASE_NODE_UNSET; } else { envelope->nReleaseNode = static_castnReleaseNode)>(nPoint); } return true; } // Enable or disable a flag of the current envelope bool CViewInstrument::EnvSetFlag(EnvelopeFlags flag, bool enable) { InstrumentEnvelope *envelope = GetEnvelopePtr(); if(envelope == nullptr || envelope->empty()) return false; bool modified = envelope->dwFlags[flag] != enable; PrepareUndo("Toggle Envelope Flag"); envelope->dwFlags.set(flag, enable); return modified; } bool CViewInstrument::EnvToggleEnv(EnvelopeType envelope, CSoundFile &sndFile, ModInstrument &ins, bool enable, EnvelopeNode::value_t defaultValue, EnvelopeFlags extraFlags) { InstrumentEnvelope &env = ins.GetEnvelope(envelope); const FlagSet flags = (ENV_ENABLED | extraFlags); env.dwFlags.set(flags, enable); if(enable && env.empty()) { env.reserve(2); env.push_back(EnvelopeNode(0, defaultValue)); env.push_back(EnvelopeNode(10, defaultValue)); InvalidateRect(NULL, FALSE); } CriticalSection cs; // Update mixing flags... for(auto &chn : sndFile.m_PlayState.Chn) { if(chn.pModInstrument == &ins) { chn.GetEnvelope(envelope).flags.set(flags, enable); } } return true; } bool CViewInstrument::EnvSetVolEnv(bool enable) { ModInstrument *pIns = GetInstrumentPtr(); if(pIns == nullptr) return false; return EnvToggleEnv(ENV_VOLUME, GetDocument()->GetSoundFile(), *pIns, enable, 64); } bool CViewInstrument::EnvSetPanEnv(bool enable) { ModInstrument *pIns = GetInstrumentPtr(); if(pIns == nullptr) return false; return EnvToggleEnv(ENV_PANNING, GetDocument()->GetSoundFile(), *pIns, enable, 32); } bool CViewInstrument::EnvSetPitchEnv(bool enable) { ModInstrument *pIns = GetInstrumentPtr(); if(pIns == nullptr) return false; pIns->PitchEnv.dwFlags.reset(ENV_FILTER); return EnvToggleEnv(ENV_PITCH, GetDocument()->GetSoundFile(), *pIns, enable, 32); } bool CViewInstrument::EnvSetFilterEnv(bool enable) { ModInstrument *pIns = GetInstrumentPtr(); if(pIns == nullptr) return false; return EnvToggleEnv(ENV_PITCH, GetDocument()->GetSoundFile(), *pIns, enable, 64, ENV_FILTER); } uint32 CViewInstrument::DragItemToEnvPoint() const { InstrumentEnvelope *pEnv = GetEnvelopePtr(); if(pEnv == nullptr || !m_nDragItem) return 0; switch(m_nDragItem) { case ENV_DRAGLOOPSTART: return pEnv->nLoopStart; case ENV_DRAGLOOPEND: return pEnv->nLoopEnd; case ENV_DRAGSUSTAINSTART: return pEnv->nSustainStart; case ENV_DRAGSUSTAINEND: return pEnv->nSustainEnd; default: return m_nDragItem - 1; } } int CViewInstrument::TickToScreen(int tick) const { return static_cast((tick * m_zoom) - m_nScrollPosX + m_envPointSize); } int CViewInstrument::PointToScreen(int nPoint) const { return TickToScreen(EnvGetTick(nPoint)); } int CViewInstrument::ScreenToTick(int x) const { int offset = m_nScrollPosX + x; if(offset < m_envPointSize) return 0; return mpt::saturate_round((offset - m_envPointSize) / m_zoom); } int CViewInstrument::ScreenToValue(int y) const { if(m_rcClient.bottom < 2) return ENVELOPE_MIN; int n = ENVELOPE_MAX - Util::muldivr(y, ENVELOPE_MAX, m_rcClient.bottom - 1); if(n < ENVELOPE_MIN) return ENVELOPE_MIN; if(n > ENVELOPE_MAX) return ENVELOPE_MAX; return n; } int CViewInstrument::ScreenToPoint(int x0, int y0) const { int nPoint = -1; int64 ydist = int64_max, xdist = int64_max; int numPoints = EnvGetNumPoints(); for(int i = 0; i < numPoints; i++) { int dx = x0 - PointToScreen(i); int64 dx2 = Util::mul32to64(dx, dx); if(dx2 <= xdist) { int dy = y0 - ValueToScreen(EnvGetValue(i)); int64 dy2 = Util::mul32to64(dy, dy); if(dx2 < xdist || (dx2 == xdist && dy2 < ydist)) { nPoint = i; xdist = dx2; ydist = dy2; } } } return nPoint; } bool CViewInstrument::GetNcButtonRect(UINT button, CRect &rect) const { rect.left = 4; rect.top = 3; rect.bottom = rect.top + ENV_LEFTBAR_CYBTN; if(button >= ENV_LEFTBAR_BUTTONS) return false; for(UINT i = 0; i < button; i++) { if(cLeftBarButtons[i] == ID_SEPARATOR) rect.left += ENV_LEFTBAR_CXSEP; else rect.left += ENV_LEFTBAR_CXBTN + ENV_LEFTBAR_CXSPC; } if(cLeftBarButtons[button] == ID_SEPARATOR) { rect.left += ENV_LEFTBAR_CXSEP / 2 - 2; rect.right = rect.left + 2; return false; } else { rect.right = rect.left + ENV_LEFTBAR_CXBTN; } return true; } UINT CViewInstrument::GetNcButtonAtPoint(CPoint point, CRect *outRect) const { CRect rect, rcWnd; UINT button = uint32_max; GetWindowRect(&rcWnd); for(UINT i = 0; i < ENV_LEFTBAR_BUTTONS; i++) { if(!(m_NcButtonState[i] & NCBTNS_DISABLED) && GetNcButtonRect(i, rect)) { rect.OffsetRect(rcWnd.left, rcWnd.top); if(rect.PtInRect(point)) { button = i; break; } } } if(outRect) *outRect = rect; return button; } void CViewInstrument::UpdateNcButtonState() { CModDoc *pModDoc = GetDocument(); if(!pModDoc) return; CSoundFile &sndFile = pModDoc->GetSoundFile(); CDC *pDC = NULL; for (UINT i=0; i= ENV_MAX_ZOOM) dwStyle |= NCBTNS_DISABLED; break; case ID_ENVELOPE_ZOOM_OUT: if (m_zoom <= ENV_MIN_ZOOM) dwStyle |= NCBTNS_DISABLED; break; case ID_ENVELOPE_LOAD: case ID_ENVELOPE_SAVE: if (GetInstrumentPtr() == nullptr) dwStyle |= NCBTNS_DISABLED; break; } if (m_nBtnMouseOver == i) { dwStyle |= NCBTNS_MOUSEOVER; if (m_dwStatus & INSSTATUS_NCLBTNDOWN) dwStyle |= NCBTNS_PUSHED; } if (dwStyle != m_NcButtonState[i]) { m_NcButtonState[i] = dwStyle; if (!pDC) pDC = GetWindowDC(); DrawNcButton(pDC, i); } } if (pDC) ReleaseDC(pDC); } //////////////////////////////////////////////////////////////////// // CViewInstrument drawing void CViewInstrument::UpdateView(UpdateHint hint, CObject *pObj) { if(pObj == this) { return; } const InstrumentHint instrHint = hint.ToType(); FlagSet hintType = instrHint.GetType(); const INSTRUMENTINDEX updateIns = instrHint.GetInstrument(); if(hintType[HINT_MPTOPTIONS | HINT_MODTYPE] || (hintType[HINT_ENVELOPE] && (m_nInstrument == updateIns || updateIns == 0))) { UpdateScrollSize(); UpdateNcButtonState(); InvalidateRect(NULL, FALSE); } } void CViewInstrument::DrawGrid(CDC *pDC, uint32 speed) { bool windowResized = false; if(m_dcGrid.m_hDC) { m_dcGrid.SelectObject(m_pbmpOldGrid); m_dcGrid.DeleteDC(); m_bmpGrid.DeleteObject(); windowResized = true; } if(windowResized || m_bGridForceRedraw || (m_nScrollPosX != m_GridScrollPos) || (speed != (UINT)m_GridSpeed) && speed > 0) { m_GridSpeed = speed; m_GridScrollPos = m_nScrollPosX; m_bGridForceRedraw = false; // create a memory based dc for drawing the grid m_dcGrid.CreateCompatibleDC(pDC); m_bmpGrid.CreateCompatibleBitmap(pDC, m_rcClient.Width(), m_rcClient.Height()); m_pbmpOldGrid = *m_dcGrid.SelectObject(&m_bmpGrid); // Do draw const int width = m_rcClient.Width(); int rowsPerBeat = 1, rowsPerMeasure = 1; const CModDoc *modDoc = GetDocument(); if(modDoc != nullptr) { rowsPerBeat = modDoc->GetSoundFile().m_nDefaultRowsPerBeat; rowsPerMeasure = modDoc->GetSoundFile().m_nDefaultRowsPerMeasure; } // Paint it black! m_dcGrid.FillSolidRect(&m_rcClient, TrackerSettings::Instance().rgbCustomColors[MODCOLOR_BACKENV]); const uint32 startTick = (ScreenToTick(0) / speed) * speed; const uint32 endTick = (ScreenToTick(width) / speed) * speed; auto oldPen = m_dcGrid.SelectStockObject(DC_PEN); for(uint32 tick = startTick, row = startTick / speed; tick <= endTick; tick += speed, row++) { if(rowsPerMeasure > 0 && row % rowsPerMeasure == 0) m_dcGrid.SetDCPenColor(RGB(0x80, 0x80, 0x80)); else if(rowsPerBeat > 0 && row % rowsPerBeat == 0) m_dcGrid.SetDCPenColor(RGB(0x55, 0x55, 0x55)); else m_dcGrid.SetDCPenColor(RGB(0x33, 0x33, 0x33)); int x = TickToScreen(tick); m_dcGrid.MoveTo(x, 0); m_dcGrid.LineTo(x, m_rcClient.bottom); } if(oldPen) m_dcGrid.SelectObject(oldPen); } pDC->BitBlt(m_rcClient.left, m_rcClient.top, m_rcClient.Width(), m_rcClient.Height(), &m_dcGrid, 0, 0, SRCCOPY); } void CViewInstrument::OnDraw(CDC *pDC) { CModDoc *pModDoc = GetDocument(); if((!pModDoc) || (!pDC)) return; // to avoid flicker, establish a memory dc, draw to it // and then BitBlt it to the destination "pDC" //check for window resize if(m_dcMemMain.GetSafeHdc() && m_rcOldClient != m_rcClient) { m_dcMemMain.SelectObject(oldBitmap); m_dcMemMain.DeleteDC(); m_bmpMemMain.DeleteObject(); } if(!m_dcMemMain.m_hDC) { m_dcMemMain.CreateCompatibleDC(pDC); if(!m_dcMemMain.m_hDC) return; } if(!m_bmpMemMain.m_hObject) { m_bmpMemMain.CreateCompatibleBitmap(pDC, m_rcClient.Width(), m_rcClient.Height()); } m_rcOldClient = m_rcClient; oldBitmap = *m_dcMemMain.SelectObject(&m_bmpMemMain); auto stockBrush = CBrush::FromHandle(GetStockBrush(DC_BRUSH)); if(m_bGrid) { DrawGrid(&m_dcMemMain, pModDoc->GetSoundFile().m_PlayState.m_nMusicSpeed); } else { // Paint it black! m_dcMemMain.FillSolidRect(&m_rcClient, TrackerSettings::Instance().rgbCustomColors[MODCOLOR_BACKENV]); } auto oldPen = m_dcMemMain.SelectObject(CMainFrame::penDarkGray); // Middle line (half volume or pitch / panning center) const int ymed = (m_rcClient.bottom - 1) / 2; m_dcMemMain.MoveTo(0, ymed); m_dcMemMain.LineTo(m_rcClient.right, ymed); // Drawing Loop Start/End if(EnvGetLoop()) { m_dcMemMain.SelectObject(m_nDragItem == ENV_DRAGLOOPSTART ? CMainFrame::penGray99 : CMainFrame::penDarkGray); int x1 = PointToScreen(EnvGetLoopStart()) - m_envPointSize / 2; m_dcMemMain.MoveTo(x1, 0); m_dcMemMain.LineTo(x1, m_rcClient.bottom); m_dcMemMain.SelectObject(m_nDragItem == ENV_DRAGLOOPEND ? CMainFrame::penGray99 : CMainFrame::penDarkGray); int x2 = PointToScreen(EnvGetLoopEnd()) + m_envPointSize / 2; m_dcMemMain.MoveTo(x2, 0); m_dcMemMain.LineTo(x2, m_rcClient.bottom); } // Drawing Sustain Start/End if(EnvGetSustain()) { m_dcMemMain.SelectObject(CMainFrame::penHalfDarkGray); int nspace = m_rcClient.bottom / 4; int n1 = EnvGetSustainStart(); int x1 = PointToScreen(n1) - m_envPointSize / 2; int y1 = ValueToScreen(EnvGetValue(n1)); m_dcMemMain.MoveTo(x1, y1 - nspace); m_dcMemMain.LineTo(x1, y1 + nspace); int n2 = EnvGetSustainEnd(); int x2 = PointToScreen(n2) + m_envPointSize / 2; int y2 = ValueToScreen(EnvGetValue(n2)); m_dcMemMain.MoveTo(x2, y2 - nspace); m_dcMemMain.LineTo(x2, y2 + nspace); } uint32 maxpoint = EnvGetNumPoints(); // Drawing Envelope if(maxpoint) { maxpoint--; m_dcMemMain.SelectObject(GetStockObject(DC_PEN)); m_dcMemMain.SetDCPenColor(TrackerSettings::Instance().rgbCustomColors[MODCOLOR_ENVELOPES]); uint32 releaseNode = EnvGetReleaseNode(); RECT rect; for(uint32 i = 0; i <= maxpoint; i++) { int x = PointToScreen(i); int y = ValueToScreen(EnvGetValue(i)); rect.left = x - m_envPointSize + 1; rect.top = y - m_envPointSize + 1; rect.right = x + m_envPointSize; rect.bottom = y + m_envPointSize; if(i) m_dcMemMain.LineTo(x, y); else m_dcMemMain.MoveTo(x, y); if(i == releaseNode) { m_dcMemMain.SetDCBrushColor(RGB(0xFF, 0x00, 0x00)); m_dcMemMain.FrameRect(&rect, stockBrush); m_dcMemMain.SetDCPenColor(TrackerSettings::Instance().rgbCustomColors[MODCOLOR_ENVELOPE_RELEASE]); } else if(i == m_nDragItem - 1) { // currently selected env point m_dcMemMain.SetDCBrushColor(RGB(0xFF, 0xFF, 0x00)); m_dcMemMain.FrameRect(&rect, stockBrush); } else { m_dcMemMain.SetDCBrushColor(RGB(0xFF, 0xFF, 0xFF)); m_dcMemMain.FrameRect(&rect, stockBrush); } } } DrawPositionMarks(); if(oldPen) m_dcMemMain.SelectObject(oldPen); pDC->BitBlt(m_rcClient.left, m_rcClient.top, m_rcClient.Width(), m_rcClient.Height(), &m_dcMemMain, 0, 0, SRCCOPY); } uint8 CViewInstrument::EnvGetReleaseNode() { InstrumentEnvelope *envelope = GetEnvelopePtr(); if(envelope == nullptr) return ENV_RELEASE_NODE_UNSET; return envelope->nReleaseNode; } bool CViewInstrument::EnvRemovePoint(uint32 nPoint) { CModDoc *pModDoc = GetDocument(); if((pModDoc) && (nPoint <= EnvGetLastPoint())) { ModInstrument *pIns = pModDoc->GetSoundFile().Instruments[m_nInstrument]; if(pIns) { InstrumentEnvelope *envelope = GetEnvelopePtr(); if(envelope == nullptr || envelope->empty()) return false; PrepareUndo("Remove Envelope Point"); envelope->erase(envelope->begin() + nPoint); if (nPoint >= envelope->size()) nPoint = envelope->size() - 1; if (envelope->nLoopStart > nPoint) envelope->nLoopStart--; if (envelope->nLoopEnd > nPoint) envelope->nLoopEnd--; if (envelope->nSustainStart > nPoint) envelope->nSustainStart--; if (envelope->nSustainEnd > nPoint) envelope->nSustainEnd--; if (envelope->nReleaseNode>nPoint && envelope->nReleaseNode != ENV_RELEASE_NODE_UNSET) envelope->nReleaseNode--; envelope->at(0).tick = 0; if(envelope->size() <= 1) { // if only one node is left, just disable the envelope completely *envelope = InstrumentEnvelope(); } SetModified(InstrumentHint().Envelope(), true); return true; } } return false; } // Insert point. Returns 0 if error occurred, else point ID + 1. uint32 CViewInstrument::EnvInsertPoint(int nTick, int nValue) { CModDoc *pModDoc = GetDocument(); if(pModDoc && nTick >= 0) { InstrumentEnvelope *envelope = GetEnvelopePtr(); if(envelope != nullptr && envelope->size() < pModDoc->GetSoundFile().GetModSpecifications().envelopePointsMax) { nValue = Clamp(nValue, ENVELOPE_MIN, ENVELOPE_MAX); if(std::binary_search(envelope->cbegin(), envelope->cend(), EnvelopeNode(static_cast(nTick), 0), [] (const EnvelopeNode &l, const EnvelopeNode &r) { return l.tick < r.tick; })) { // Don't want to insert a node at the same position as another node. return 0; } uint8 defaultValue; switch(m_nEnv) { case ENV_VOLUME: defaultValue = ENVELOPE_MAX; break; case ENV_PANNING: defaultValue = ENVELOPE_MID; break; case ENV_PITCH: defaultValue = envelope->dwFlags[ENV_FILTER] ? ENVELOPE_MAX : ENVELOPE_MID; break; default: return 0; } PrepareUndo("Insert Envelope Point"); if(envelope->empty()) { envelope->reserve(2); envelope->push_back(EnvelopeNode(0, defaultValue)); envelope->dwFlags.set(ENV_ENABLED); if(nTick == 0) { // Can't insert two points on the same tick! nTick = 16; } } uint32 i = 0; for(i = 0; i < envelope->size(); i++) if(nTick <= envelope->at(i).tick) break; envelope->insert(envelope->begin() + i, EnvelopeNode(mpt::saturate_cast(nTick), static_cast(nValue))); if(envelope->nLoopStart >= i) envelope->nLoopStart++; if(envelope->nLoopEnd >= i) envelope->nLoopEnd++; if(envelope->nSustainStart >= i) envelope->nSustainStart++; if(envelope->nSustainEnd >= i) envelope->nSustainEnd++; if(envelope->nReleaseNode >= i && envelope->nReleaseNode != ENV_RELEASE_NODE_UNSET) envelope->nReleaseNode++; SetModified(InstrumentHint().Envelope(), true); return i + 1; } } return 0; } void CViewInstrument::DrawPositionMarks() { CRect rect; for(auto pos : m_dwNotifyPos) if (pos != Notification::PosInvalid) { rect.top = -2; rect.left = TickToScreen(pos); rect.right = rect.left + 1; rect.bottom = m_rcClient.bottom + 1; InvertRect(m_dcMemMain.m_hDC, &rect); } } LRESULT CViewInstrument::OnPlayerNotify(Notification *pnotify) { Notification::Type type; CModDoc *pModDoc = GetDocument(); if((!pnotify) || (!pModDoc)) return 0; switch(m_nEnv) { case ENV_PANNING: type = Notification::PanEnv; break; case ENV_PITCH: type = Notification::PitchEnv; break; default: type = Notification::VolEnv; break; } if(pnotify->type[Notification::Stop]) { bool invalidate = false; for(auto &pos : m_dwNotifyPos) { if(pos != (uint32)Notification::PosInvalid) { pos = (uint32)Notification::PosInvalid; invalidate = true; } } if(invalidate) { InvalidateEnvelope(); } m_baPlayingNote.reset(); } else if(pnotify->type[type] && pnotify->item == m_nInstrument) { bool update = false; for(CHANNELINDEX i = 0; i < MAX_CHANNELS; i++) { uint32 newpos = (uint32)pnotify->pos[i]; if(m_dwNotifyPos[i] != newpos) { update = true; break; } } if(update) { HDC hdc = ::GetDC(m_hWnd); DrawPositionMarks(); for(CHANNELINDEX j = 0; j < MAX_CHANNELS; j++) { uint32 newpos = (uint32)pnotify->pos[j]; m_dwNotifyPos[j] = newpos; } DrawPositionMarks(); BitBlt(hdc, m_rcClient.left, m_rcClient.top, m_rcClient.Width(), m_rcClient.Height(), m_dcMemMain.GetSafeHdc(), 0, 0, SRCCOPY); ::ReleaseDC(m_hWnd, hdc); } } return 0; } void CViewInstrument::DrawNcButton(CDC *pDC, UINT nBtn) { CRect rect; COLORREF crHi = GetSysColor(COLOR_3DHILIGHT); COLORREF crDk = GetSysColor(COLOR_3DSHADOW); COLORREF crFc = GetSysColor(COLOR_3DFACE); COLORREF c1, c2; if(GetNcButtonRect(nBtn, rect)) { DWORD dwStyle = m_NcButtonState[nBtn]; COLORREF c3, c4; int xofs = 0, yofs = 0, nImage = 0; c1 = c2 = c3 = c4 = crFc; if(!(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_FLATBUTTONS)) { c1 = c3 = crHi; c2 = crDk; c4 = RGB(0, 0, 0); } if(dwStyle & (NCBTNS_PUSHED | NCBTNS_CHECKED)) { c1 = crDk; c2 = crHi; if(!(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_FLATBUTTONS)) { c4 = crHi; c3 = (dwStyle & NCBTNS_PUSHED) ? RGB(0, 0, 0) : crDk; } xofs = yofs = 1; } else if((dwStyle & NCBTNS_MOUSEOVER) && (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_FLATBUTTONS)) { c1 = crHi; c2 = crDk; } switch(cLeftBarButtons[nBtn]) { case ID_ENVSEL_VOLUME: nImage = IIMAGE_VOLENV; break; case ID_ENVSEL_PANNING: nImage = IIMAGE_PANENV; break; case ID_ENVSEL_PITCH: nImage = (dwStyle & NCBTNS_DISABLED) ? IIMAGE_NOPITCHENV : IIMAGE_PITCHENV; break; case ID_ENVELOPE_SETLOOP: nImage = IIMAGE_LOOP; break; case ID_ENVELOPE_SUSTAIN: nImage = IIMAGE_SUSTAIN; break; case ID_ENVELOPE_CARRY: nImage = (dwStyle & NCBTNS_DISABLED) ? IIMAGE_NOCARRY : IIMAGE_CARRY; break; case ID_ENVELOPE_VOLUME: nImage = IIMAGE_VOLSWITCH; break; case ID_ENVELOPE_PANNING: nImage = IIMAGE_PANSWITCH; break; case ID_ENVELOPE_PITCH: nImage = (dwStyle & NCBTNS_DISABLED) ? IIMAGE_NOPITCHSWITCH : IIMAGE_PITCHSWITCH; break; case ID_ENVELOPE_FILTER: nImage = (dwStyle & NCBTNS_DISABLED) ? IIMAGE_NOFILTERSWITCH : IIMAGE_FILTERSWITCH; break; case ID_INSTRUMENT_SAMPLEMAP: nImage = IIMAGE_SAMPLEMAP; break; case ID_ENVELOPE_VIEWGRID: nImage = IIMAGE_GRID; break; case ID_ENVELOPE_ZOOM_IN: nImage = (dwStyle & NCBTNS_DISABLED) ? IIMAGE_NOZOOMIN : IIMAGE_ZOOMIN; break; case ID_ENVELOPE_ZOOM_OUT: nImage = (dwStyle & NCBTNS_DISABLED) ? IIMAGE_NOZOOMOUT : IIMAGE_ZOOMOUT; break; case ID_ENVELOPE_LOAD: nImage = IIMAGE_LOAD; break; case ID_ENVELOPE_SAVE: nImage = IIMAGE_SAVE; break; } pDC->Draw3dRect(rect.left - 1, rect.top - 1, ENV_LEFTBAR_CXBTN + 2, ENV_LEFTBAR_CYBTN + 2, c3, c4); pDC->Draw3dRect(rect.left, rect.top, ENV_LEFTBAR_CXBTN, ENV_LEFTBAR_CYBTN, c1, c2); rect.DeflateRect(1, 1); pDC->FillSolidRect(&rect, crFc); rect.left += xofs; rect.top += yofs; if(dwStyle & NCBTNS_CHECKED) m_bmpEnvBar.Draw(pDC, IIMAGE_CHECKED, rect.TopLeft(), ILD_NORMAL); m_bmpEnvBar.Draw(pDC, nImage, rect.TopLeft(), ILD_NORMAL); } else { c1 = c2 = crFc; if(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_FLATBUTTONS) { c1 = crDk; c2 = crHi; } pDC->Draw3dRect(rect.left, rect.top, 2, ENV_LEFTBAR_CYBTN, c1, c2); } } void CViewInstrument::OnNcPaint() { RECT rect; CModScrollView::OnNcPaint(); GetWindowRect(&rect); // Assumes there is no other non-client items rect.bottom = ENV_LEFTBAR_CY; rect.right -= rect.left; rect.left = 0; rect.top = 0; if((rect.left < rect.right) && (rect.top < rect.bottom)) { CDC *pDC = GetWindowDC(); { // Shadow auto shadowRect = rect; shadowRect.top = shadowRect.bottom - 1; pDC->FillSolidRect(&shadowRect, GetSysColor(COLOR_BTNSHADOW)); } rect.bottom--; if(rect.top < rect.bottom) pDC->FillSolidRect(&rect, GetSysColor(COLOR_BTNFACE)); if(rect.top + 2 < rect.bottom) { for(UINT i = 0; i < ENV_LEFTBAR_BUTTONS; i++) { DrawNcButton(pDC, i); } } ReleaseDC(pDC); } } //////////////////////////////////////////////////////////////////// // CViewInstrument messages void CViewInstrument::OnSize(UINT nType, int cx, int cy) { CModScrollView::OnSize(nType, cx, cy); if(((nType == SIZE_RESTORED) || (nType == SIZE_MAXIMIZED)) && (cx > 0) && (cy > 0)) { UpdateScrollSize(); } } void CViewInstrument::OnNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS *lpncsp) { CModScrollView::OnNcCalcSize(bCalcValidRects, lpncsp); if(lpncsp) { lpncsp->rgrc[0].top += ENV_LEFTBAR_CY; if(lpncsp->rgrc[0].bottom < lpncsp->rgrc[0].top) lpncsp->rgrc[0].top = lpncsp->rgrc[0].bottom; } } void CViewInstrument::OnNcMouseMove(UINT nHitTest, CPoint point) { const auto button = GetNcButtonAtPoint(point); if(button != m_nBtnMouseOver) { CMainFrame *pMainFrm = CMainFrame::GetMainFrame(); if(pMainFrm) { CString strText; if(button < ENV_LEFTBAR_BUTTONS && cLeftBarButtons[button] != ID_SEPARATOR) { strText = LoadResourceString(cLeftBarButtons[button]); } pMainFrm->SetHelpText(strText); } m_nBtnMouseOver = button; UpdateNcButtonState(); } CModScrollView::OnNcMouseMove(nHitTest, point); } void CViewInstrument::OnNcLButtonDown(UINT uFlags, CPoint point) { if(m_nBtnMouseOver < ENV_LEFTBAR_BUTTONS) { m_dwStatus |= INSSTATUS_NCLBTNDOWN; if(cLeftBarButtons[m_nBtnMouseOver] != ID_SEPARATOR) { PostMessage(WM_COMMAND, cLeftBarButtons[m_nBtnMouseOver]); UpdateNcButtonState(); } } CModScrollView::OnNcLButtonDown(uFlags, point); } void CViewInstrument::OnNcLButtonUp(UINT uFlags, CPoint point) { if(m_dwStatus & INSSTATUS_NCLBTNDOWN) { m_dwStatus &= ~INSSTATUS_NCLBTNDOWN; UpdateNcButtonState(); } CModScrollView::OnNcLButtonUp(uFlags, point); } void CViewInstrument::OnNcLButtonDblClk(UINT uFlags, CPoint point) { OnNcLButtonDown(uFlags, point); } LRESULT CViewInstrument::OnNcHitTest(CPoint point) { CRect rect; GetWindowRect(&rect); rect.bottom = rect.top + ENV_LEFTBAR_CY; if(rect.PtInRect(point)) { return HTBORDER; } return CModScrollView::OnNcHitTest(point); } void CViewInstrument::OnMouseMove(UINT, CPoint pt) { ModInstrument *pIns = GetInstrumentPtr(); if(pIns == nullptr) return; bool splitCursor = false; if((m_nBtnMouseOver < ENV_LEFTBAR_BUTTONS) || (m_dwStatus & INSSTATUS_NCLBTNDOWN)) { m_dwStatus &= ~INSSTATUS_NCLBTNDOWN; m_nBtnMouseOver = 0xFFFF; UpdateNcButtonState(); CMainFrame *pMainFrm = CMainFrame::GetMainFrame(); if(pMainFrm) pMainFrm->SetHelpText(_T("")); } int nTick = ScreenToTick(pt.x); int nVal = Clamp(ScreenToValue(pt.y), ENVELOPE_MIN, ENVELOPE_MAX); if(nTick < 0) nTick = 0; UpdateIndicator(nTick, nVal); if((m_dwStatus & INSSTATUS_DRAGGING) && (m_nDragItem)) { if(!m_mouseMoveModified) { PrepareUndo("Move Envelope Point"); m_mouseMoveModified = true; } bool changed = false; if(pt.x >= m_rcClient.right - 2) nTick++; if(IsDragItemEnvPoint()) { // Ctrl pressed -> move tail of envelope changed = EnvSetValue(m_nDragItem - 1, nTick, nVal, CMainFrame::GetInputHandler()->CtrlPressed()); } else { int nPoint = ScreenToPoint(pt.x, pt.y); if (nPoint >= 0) switch(m_nDragItem) { case ENV_DRAGLOOPSTART: changed = EnvSetLoopStart(nPoint); splitCursor = true; break; case ENV_DRAGLOOPEND: changed = EnvSetLoopEnd(nPoint); splitCursor = true; break; case ENV_DRAGSUSTAINSTART: changed = EnvSetSustainStart(nPoint); splitCursor = true; break; case ENV_DRAGSUSTAINEND: changed = EnvSetSustainEnd(nPoint); splitCursor = true; break; } } if(changed) { if(pt.x <= 0) { UpdateScrollSize(); OnScrollBy(CSize(pt.x - (int)m_zoom, 0), TRUE); } if(pt.x >= m_rcClient.right - 1) { UpdateScrollSize(); OnScrollBy(CSize((int)m_zoom + pt.x - m_rcClient.right, 0), TRUE); } SetModified(InstrumentHint().Envelope(), true); UpdateWindow(); //rewbs: TODO - optimisation here so we don't redraw whole view. } } else { CRect rect; if(EnvGetSustain()) { int nspace = m_rcClient.bottom / 4; rect.top = ValueToScreen(EnvGetValue(EnvGetSustainStart())) - nspace; rect.bottom = rect.top + nspace * 2; rect.right = PointToScreen(EnvGetSustainStart()) + 1; rect.left = rect.right - m_envPointSize * 2; if(rect.PtInRect(pt)) { splitCursor = true; // ENV_DRAGSUSTAINSTART; } else { rect.top = ValueToScreen(EnvGetValue(EnvGetSustainEnd())) - nspace; rect.bottom = rect.top + nspace * 2; rect.left = PointToScreen(EnvGetSustainEnd()) - 1; rect.right = rect.left + m_envPointSize * 2; if(rect.PtInRect(pt)) splitCursor = true; // ENV_DRAGSUSTAINEND; } } if(EnvGetLoop()) { rect.top = m_rcClient.top; rect.bottom = m_rcClient.bottom; rect.right = PointToScreen(EnvGetLoopStart()) + 1; rect.left = rect.right - m_envPointSize * 2; if(rect.PtInRect(pt)) { splitCursor = true; // ENV_DRAGLOOPSTART; } else { rect.left = PointToScreen(EnvGetLoopEnd()) - 1; rect.right = rect.left + m_envPointSize * 2; if(rect.PtInRect(pt)) splitCursor = true; // ENV_DRAGLOOPEND; } } } // Update the mouse cursor if(splitCursor) { if(!(m_dwStatus & INSSTATUS_SPLITCURSOR)) { m_dwStatus |= INSSTATUS_SPLITCURSOR; if(!(m_dwStatus & INSSTATUS_DRAGGING)) SetCapture(); SetCursor(CMainFrame::curVSplit); } } else { if(m_dwStatus & INSSTATUS_SPLITCURSOR) { m_dwStatus &= ~INSSTATUS_SPLITCURSOR; SetCursor(CMainFrame::curArrow); if(!(m_dwStatus & INSSTATUS_DRAGGING)) ReleaseCapture(); } } } void CViewInstrument::UpdateIndicator() { InstrumentEnvelope *pEnv = GetEnvelopePtr(); if(pEnv == nullptr || !m_nDragItem) return; uint32 point = DragItemToEnvPoint(); if(point < pEnv->size()) { UpdateIndicator(pEnv->at(point).tick, pEnv->at(point).value); } } void CViewInstrument::UpdateIndicator(int tick, int val) { ModInstrument *pIns = GetInstrumentPtr(); if(pIns == nullptr) return; CString s; s.Format(TrackerSettings::Instance().cursorPositionInHex ? _T("Tick %X, [%s]") : _T("Tick %d, [%s]"), tick, EnvValueToString(tick, val).GetString()); CModScrollView::UpdateIndicator(s); CMainFrame::GetMainFrame()->NotifyAccessibilityUpdate(*this); } CString CViewInstrument::EnvValueToString(int tick, int val) const { const InstrumentEnvelope *env = GetEnvelopePtr(); const bool hasReleaseNode = env->nReleaseNode != ENV_RELEASE_NODE_UNSET; EnvelopeNode releaseNode; if(hasReleaseNode) { releaseNode = env->at(env->nReleaseNode); } CString s; if(!hasReleaseNode || tick <= releaseNode.tick + 1) { // ticks before release node (or no release node) const int displayVal = (m_nEnv != ENV_VOLUME && !(m_nEnv == ENV_PITCH && env->dwFlags[ENV_FILTER])) ? val - 32 : val; if(m_nEnv != ENV_PANNING) s.Format(_T("%d"), displayVal); else // panning envelope: display right/center/left chars s.Format(_T("%d %c"), std::abs(displayVal), displayVal > 0 ? _T('R') : (displayVal < 0 ? _T('L') : _T('C'))); } else { // ticks after release node int displayVal = (val - releaseNode.value) * 2; displayVal = (m_nEnv != ENV_VOLUME) ? displayVal - 32 : displayVal; s.Format(_T("Rel%c%d"), displayVal > 0 ? _T('+') : _T('-'), std::abs(displayVal)); } return s; } void CViewInstrument::OnLButtonDown(UINT, CPoint pt) { m_mouseMoveModified = false; if(!(m_dwStatus & INSSTATUS_DRAGGING)) { CRect rect; // Look if dragging a point uint32 maxpoint = EnvGetLastPoint(); uint32 oldDragItem = m_nDragItem; m_nDragItem = 0; const int hitboxSize = static_cast((6 * m_nDPIx) / 96.0f); for(uint32 i = 0; i <= maxpoint; i++) { int x = PointToScreen(i); int y = ValueToScreen(EnvGetValue(i)); rect.SetRect(x - hitboxSize, y - hitboxSize, x + hitboxSize + 1, y + hitboxSize + 1); if(rect.PtInRect(pt)) { m_nDragItem = i + 1; break; } } if((!m_nDragItem) && (EnvGetSustain())) { int nspace = m_rcClient.bottom / 4; rect.top = ValueToScreen(EnvGetValue(EnvGetSustainStart())) - nspace; rect.bottom = rect.top + nspace * 2; rect.right = PointToScreen(EnvGetSustainStart()) + 1; rect.left = rect.right - m_envPointSize * 2; if(rect.PtInRect(pt)) { m_nDragItem = ENV_DRAGSUSTAINSTART; } else { rect.top = ValueToScreen(EnvGetValue(EnvGetSustainEnd())) - nspace; rect.bottom = rect.top + nspace * 2; rect.left = PointToScreen(EnvGetSustainEnd()) - 1; rect.right = rect.left + m_envPointSize * 2; if(rect.PtInRect(pt)) m_nDragItem = ENV_DRAGSUSTAINEND; } } if((!m_nDragItem) && (EnvGetLoop())) { rect.top = m_rcClient.top; rect.bottom = m_rcClient.bottom; rect.right = PointToScreen(EnvGetLoopStart()) + 1; rect.left = rect.right - m_envPointSize * 2; if(rect.PtInRect(pt)) { m_nDragItem = ENV_DRAGLOOPSTART; } else { rect.left = PointToScreen(EnvGetLoopEnd()) - 1; rect.right = rect.left + m_envPointSize * 2; if(rect.PtInRect(pt)) m_nDragItem = ENV_DRAGLOOPEND; } } if(m_nDragItem) { SetCapture(); m_dwStatus |= INSSTATUS_DRAGGING; // refresh active node colour InvalidateRect(NULL, FALSE); } else { // Shift-Click: Insert envelope point here if(CMainFrame::GetInputHandler()->ShiftPressed()) { if(InsertAtPoint(pt) == 0 && oldDragItem != 0) { InvalidateRect(NULL, FALSE); } } else if(oldDragItem) { InvalidateRect(NULL, FALSE); } } } } void CViewInstrument::OnLButtonUp(UINT, CPoint) { m_mouseMoveModified = false; if(m_dwStatus & INSSTATUS_SPLITCURSOR) { m_dwStatus &= ~INSSTATUS_SPLITCURSOR; SetCursor(CMainFrame::curArrow); } if(m_dwStatus & INSSTATUS_DRAGGING) { m_dwStatus &= ~INSSTATUS_DRAGGING; ReleaseCapture(); } } void CViewInstrument::OnRButtonDown(UINT flags, CPoint pt) { const CModDoc *pModDoc = GetDocument(); if(!pModDoc) return; const CSoundFile &sndFile = GetDocument()->GetSoundFile(); if(m_dwStatus & INSSTATUS_DRAGGING) return; // Ctrl + Right-Click = Delete point if(flags & MK_CONTROL) { OnMButtonDown(flags, pt); return; } CMenu Menu; if((pModDoc) && (Menu.LoadMenu(IDR_ENVELOPES))) { CMenu *pSubMenu = Menu.GetSubMenu(0); if(pSubMenu != nullptr) { m_nDragItem = ScreenToPoint(pt.x, pt.y) + 1; const uint32 maxPoint = (sndFile.GetType() == MOD_TYPE_XM) ? 11 : 24; const uint32 lastpoint = EnvGetLastPoint(); const bool forceRelease = !sndFile.GetModSpecifications().hasReleaseNode && (EnvGetReleaseNode() != ENV_RELEASE_NODE_UNSET); pSubMenu->EnableMenuItem(ID_ENVELOPE_INSERTPOINT, (lastpoint < maxPoint) ? MF_ENABLED : MF_GRAYED); pSubMenu->EnableMenuItem(ID_ENVELOPE_REMOVEPOINT, ((m_nDragItem) && (lastpoint > 0)) ? MF_ENABLED : MF_GRAYED); pSubMenu->EnableMenuItem(ID_ENVELOPE_CARRY, (sndFile.GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) ? MF_ENABLED : MF_GRAYED); pSubMenu->EnableMenuItem(ID_ENVELOPE_TOGGLERELEASENODE, ((sndFile.GetModSpecifications().hasReleaseNode && m_nEnv == ENV_VOLUME) || forceRelease) ? MF_ENABLED : MF_GRAYED); pSubMenu->CheckMenuItem(ID_ENVELOPE_SETLOOP, (EnvGetLoop()) ? MF_CHECKED : MF_UNCHECKED); pSubMenu->CheckMenuItem(ID_ENVELOPE_SUSTAIN, (EnvGetSustain()) ? MF_CHECKED : MF_UNCHECKED); pSubMenu->CheckMenuItem(ID_ENVELOPE_CARRY, (EnvGetCarry()) ? MF_CHECKED : MF_UNCHECKED); pSubMenu->CheckMenuItem(ID_ENVELOPE_TOGGLERELEASENODE, (EnvGetReleaseNode() == m_nDragItem - 1) ? MF_CHECKED : MF_UNCHECKED); m_ptMenu = pt; ClientToScreen(&pt); pSubMenu->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, this); } } } void CViewInstrument::OnMButtonDown(UINT, CPoint pt) { // Middle mouse button: Remove envelope point int point = ScreenToPoint(pt.x, pt.y); if(point >= 0) { EnvRemovePoint(point); m_nDragItem = point + 1; } } void CViewInstrument::OnPrevInstrument() { SendCtrlMessage(CTRLMSG_INS_PREVINSTRUMENT); } void CViewInstrument::OnNextInstrument() { SendCtrlMessage(CTRLMSG_INS_NEXTINSTRUMENT); } void CViewInstrument::OnEditSampleMap() { SendCtrlMessage(CTRLMSG_INS_SAMPLEMAP); } void CViewInstrument::OnSelectVolumeEnv() { if(m_nEnv != ENV_VOLUME) SetCurrentInstrument(m_nInstrument, ENV_VOLUME); } void CViewInstrument::OnSelectPanningEnv() { if(m_nEnv != ENV_PANNING) SetCurrentInstrument(m_nInstrument, ENV_PANNING); } void CViewInstrument::OnSelectPitchEnv() { if(m_nEnv != ENV_PITCH) SetCurrentInstrument(m_nInstrument, ENV_PITCH); } void CViewInstrument::OnEnvLoopChanged() { CModDoc *pModDoc = GetDocument(); PrepareUndo("Toggle Envelope Loop"); if((pModDoc) && (EnvSetLoop(!EnvGetLoop()))) { InstrumentEnvelope *pEnv = GetEnvelopePtr(); if(EnvGetLoop() && pEnv != nullptr && pEnv->nLoopEnd == 0) { // Enabled loop => set loop points if no loop has been specified yet. pEnv->nLoopStart = 0; pEnv->nLoopEnd = mpt::saturate_castnLoopEnd)>(pEnv->size() - 1); } SetModified(InstrumentHint().Envelope(), true); } } void CViewInstrument::OnEnvSustainChanged() { CModDoc *pModDoc = GetDocument(); PrepareUndo("Toggle Envelope Sustain"); if((pModDoc) && (EnvSetSustain(!EnvGetSustain()))) { InstrumentEnvelope *pEnv = GetEnvelopePtr(); if(EnvGetSustain() && pEnv != nullptr && pEnv->nSustainStart == pEnv->nSustainEnd && IsDragItemEnvPoint()) { // Enabled sustain loop => set sustain loop points if no sustain loop has been specified yet. pEnv->nSustainStart = pEnv->nSustainEnd = mpt::saturate_castnSustainEnd)>(m_nDragItem - 1); } SetModified(InstrumentHint().Envelope(), true); } } void CViewInstrument::OnEnvCarryChanged() { CModDoc *pModDoc = GetDocument(); PrepareUndo("Toggle Envelope Carry"); if((pModDoc) && (EnvSetCarry(!EnvGetCarry()))) { SetModified(InstrumentHint().Envelope(), false); UpdateNcButtonState(); } } void CViewInstrument::OnEnvToggleReleasNode() { if(IsDragItemEnvPoint()) { PrepareUndo("Toggle Envelope Release Node"); if(EnvToggleReleaseNode(m_nDragItem - 1)) { SetModified(InstrumentHint().Envelope(), true); } } } void CViewInstrument::OnEnvVolChanged() { GetDocument()->GetInstrumentUndo().PrepareUndo(m_nInstrument, "Toggle Volume Envelope", ENV_VOLUME); if(EnvSetVolEnv(!EnvGetVolEnv())) { SetModified(InstrumentHint().Envelope(), false); UpdateNcButtonState(); } } void CViewInstrument::OnEnvPanChanged() { GetDocument()->GetInstrumentUndo().PrepareUndo(m_nInstrument, "Toggle Panning Envelope", ENV_PANNING); if(EnvSetPanEnv(!EnvGetPanEnv())) { SetModified(InstrumentHint().Envelope(), false); UpdateNcButtonState(); } } void CViewInstrument::OnEnvPitchChanged() { GetDocument()->GetInstrumentUndo().PrepareUndo(m_nInstrument, "Toggle Pitch Envelope", ENV_PITCH); if(EnvSetPitchEnv(!EnvGetPitchEnv())) { SetModified(InstrumentHint().Envelope(), false); UpdateNcButtonState(); } } void CViewInstrument::OnEnvFilterChanged() { GetDocument()->GetInstrumentUndo().PrepareUndo(m_nInstrument, "Toggle Filter Envelope", ENV_PITCH); if(EnvSetFilterEnv(!EnvGetFilterEnv())) { SetModified(InstrumentHint().Envelope(), false); UpdateNcButtonState(); } } void CViewInstrument::OnEnvToggleGrid() { m_bGrid = !m_bGrid; if(m_bGrid) m_bGridForceRedraw = true; CModDoc *pModDoc = GetDocument(); if(pModDoc) pModDoc->UpdateAllViews(nullptr, InstrumentHint(m_nInstrument).Envelope()); } void CViewInstrument::OnEnvRemovePoint() { if(m_nDragItem > 0) { EnvRemovePoint(m_nDragItem - 1); } } void CViewInstrument::OnEnvInsertPoint() { const int tick = ScreenToTick(m_ptMenu.x), value = ScreenToValue(m_ptMenu.y); if(!EnvInsertPoint(tick, value)) { // Couldn't insert point, maybe because there's already a point at this tick // => Try next tick EnvInsertPoint(tick + 1, value); } } bool CViewInstrument::InsertAtPoint(CPoint pt) { auto item = EnvInsertPoint(ScreenToTick(pt.x), ScreenToValue(pt.y)); // returns point ID + 1 if successful, else 0. if(item > 0) { // Drag point if successful SetCapture(); m_dwStatus |= INSSTATUS_DRAGGING; m_nDragItem = item; } return item > 0; } void CViewInstrument::OnEditCopy() { CModDoc *pModDoc = GetDocument(); if(pModDoc) pModDoc->CopyEnvelope(m_nInstrument, m_nEnv); } void CViewInstrument::OnEditPaste() { CModDoc *pModDoc = GetDocument(); PrepareUndo("Paste Envelope"); if(pModDoc->PasteEnvelope(m_nInstrument, m_nEnv)) { SetModified(InstrumentHint().Envelope(), true); } else { pModDoc->GetInstrumentUndo().RemoveLastUndoStep(m_nInstrument); } } void CViewInstrument::PlayNote(ModCommand::NOTE note) { CMainFrame *pMainFrm = CMainFrame::GetMainFrame(); CModDoc *pModDoc = GetDocument(); if(pModDoc == nullptr || pMainFrm == nullptr) { return; } if(note > 0 && note < 128) { if(m_nInstrument && !m_baPlayingNote[note]) { CSoundFile &sndFile = pModDoc->GetSoundFile(); ModInstrument *pIns = sndFile.Instruments[m_nInstrument]; if((!pIns) || (!pIns->Keyboard[note - NOTE_MIN] && !pIns->nMixPlug)) return; { if(pMainFrm->GetModPlaying() != pModDoc) { sndFile.m_SongFlags.set(SONG_PAUSED); sndFile.ResetChannels(); if(!pMainFrm->PlayMod(pModDoc)) return; } pModDoc->PlayNote(PlayNoteParam(note).Instrument(m_nInstrument).CheckNNA(m_baPlayingNote), &m_noteChannel); } CString noteName; if(ModCommand::IsNote(note)) { noteName = mpt::ToCString(sndFile.GetNoteName(note, m_nInstrument)); } pMainFrm->SetInfoText(noteName); } } else { pModDoc->PlayNote(PlayNoteParam(note).Instrument(m_nInstrument)); } } // Drop files from Windows void CViewInstrument::OnDropFiles(HDROP hDropInfo) { const UINT nFiles = ::DragQueryFile(hDropInfo, (UINT)-1, NULL, 0); CMainFrame::GetMainFrame()->SetForegroundWindow(); for(UINT f = 0; f < nFiles; f++) { UINT size = ::DragQueryFile(hDropInfo, f, nullptr, 0) + 1; std::vector fileName(size, _T('\0')); if(::DragQueryFile(hDropInfo, f, fileName.data(), size)) { const mpt::PathString file = mpt::PathString::FromNative(fileName.data()); PrepareUndo("Replace Envelope"); if(GetDocument()->LoadEnvelope(m_nInstrument, m_nEnv, file)) { SetModified(InstrumentHint(m_nInstrument).Envelope(), true); } else { GetDocument()->GetInstrumentUndo().RemoveLastUndoStep(m_nInstrument); if(SendCtrlMessage(CTRLMSG_INS_OPENFILE, (LPARAM)&file) && f < nFiles - 1) { // Insert more instrument slots if(!SendCtrlMessage(CTRLMSG_INS_NEWINSTRUMENT)) break; } } } } ::DragFinish(hDropInfo); } BOOL CViewInstrument::OnDragonDrop(BOOL doDrop, const DRAGONDROP *dropInfo) { CModDoc *modDoc = GetDocument(); bool canDrop = false; if((!dropInfo) || (!modDoc)) return FALSE; CSoundFile &sndFile = modDoc->GetSoundFile(); switch(dropInfo->dropType) { case DRAGONDROP_INSTRUMENT: if(dropInfo->sndFile == &sndFile) { canDrop = ((dropInfo->dropItem) && (dropInfo->dropItem <= sndFile.m_nInstruments) && (dropInfo->sndFile == &sndFile)); } else { canDrop = ((dropInfo->dropItem) && ((dropInfo->dropParam) || (dropInfo->sndFile))); } break; case DRAGONDROP_DLS: canDrop = ((dropInfo->dropItem < CTrackApp::gpDLSBanks.size()) && (CTrackApp::gpDLSBanks[dropInfo->dropItem])); break; case DRAGONDROP_SOUNDFILE: case DRAGONDROP_MIDIINSTR: canDrop = !dropInfo->GetPath().empty(); break; } const bool insertNew = CMainFrame::GetInputHandler()->ShiftPressed() && sndFile.GetNumInstruments() > 0; if(insertNew && !sndFile.CanAddMoreInstruments()) canDrop = false; if(!canDrop || !doDrop) return canDrop; if(!sndFile.GetNumInstruments() && sndFile.GetModSpecifications().instrumentsMax > 0) SendCtrlMessage(CTRLMSG_INS_NEWINSTRUMENT); if(!m_nInstrument || m_nInstrument > sndFile.GetNumInstruments()) return FALSE; // Do the drop bool modified = false; BeginWaitCursor(); switch(dropInfo->dropType) { case DRAGONDROP_INSTRUMENT: if(dropInfo->sndFile == &sndFile) { SendCtrlMessage(CTRLMSG_SETCURRENTINSTRUMENT, dropInfo->dropItem); } else { if(insertNew && !SendCtrlMessage(CTRLMSG_INS_NEWINSTRUMENT)) canDrop = false; else SendCtrlMessage(CTRLMSG_INS_SONGDROP, reinterpret_cast(dropInfo)); } break; case DRAGONDROP_MIDIINSTR: if(CDLSBank::IsDLSBank(dropInfo->GetPath())) { CDLSBank dlsbank; if(dlsbank.Open(dropInfo->GetPath())) { const DLSINSTRUMENT *pDlsIns; UINT nIns = 0, nRgn = 0xFF; // Drums if(dropInfo->dropItem & 0x80) { UINT key = dropInfo->dropItem & 0x7F; pDlsIns = dlsbank.FindInstrument(true, 0xFFFF, 0xFF, key, &nIns); if(pDlsIns) nRgn = dlsbank.GetRegionFromKey(nIns, key); } else // Melodic { pDlsIns = dlsbank.FindInstrument(false, 0xFFFF, dropInfo->dropItem, 60, &nIns); if(pDlsIns) nRgn = dlsbank.GetRegionFromKey(nIns, 60); } canDrop = false; if(pDlsIns) { if(!insertNew || SendCtrlMessage(CTRLMSG_INS_NEWINSTRUMENT)) { CriticalSection cs; modDoc->GetInstrumentUndo().PrepareUndo(m_nInstrument, "Replace Instrument"); canDrop = modified = dlsbank.ExtractInstrument(sndFile, m_nInstrument, nIns, nRgn); } } break; } } // Instrument file -> fall through [[fallthrough]]; case DRAGONDROP_SOUNDFILE: if(!insertNew || SendCtrlMessage(CTRLMSG_INS_NEWINSTRUMENT)) SendCtrlMessage(CTRLMSG_INS_OPENFILE, dropInfo->dropParam); break; case DRAGONDROP_DLS: { UINT nIns = dropInfo->dropParam & 0xFFFF; uint32 drumRgn = uint32_max; // Drums: (0x80000000) | (Region << 16) | (Instrument) if(dropInfo->dropParam & 0x80000000) drumRgn = (dropInfo->dropParam & 0x7FFF0000) >> 16; if(!insertNew || SendCtrlMessage(CTRLMSG_INS_NEWINSTRUMENT)) { CriticalSection cs; modDoc->GetInstrumentUndo().PrepareUndo(m_nInstrument, "Replace Instrument"); canDrop = modified = CTrackApp::gpDLSBanks[dropInfo->dropItem]->ExtractInstrument(sndFile, m_nInstrument, nIns, drumRgn); } } break; } if(modified) { SetModified(InstrumentHint().Info().Envelope().Names(), true); GetDocument()->UpdateAllViews(nullptr, SampleHint().Info().Names().Data(), this); } CMDIChildWnd *pMDIFrame = (CMDIChildWnd *)GetParentFrame(); if(pMDIFrame) { pMDIFrame->MDIActivate(); pMDIFrame->SetActiveView(this); SetFocus(); } EndWaitCursor(); return canDrop; } LRESULT CViewInstrument::OnMidiMsg(WPARAM midiDataParam, LPARAM) { const uint32 midiData = static_cast(midiDataParam); CModDoc *modDoc = GetDocument(); if(modDoc != nullptr) { modDoc->ProcessMIDI(midiData, m_nInstrument, modDoc->GetSoundFile().GetInstrumentPlugin(m_nInstrument), kCtxViewInstruments); MIDIEvents::EventType event = MIDIEvents::GetTypeFromEvent(midiData); uint8 midiByte1 = MIDIEvents::GetDataByte1FromEvent(midiData); if(event == MIDIEvents::evNoteOn) { CMainFrame::GetMainFrame()->SetInfoText(mpt::ToCString(modDoc->GetSoundFile().GetNoteName(midiByte1 + NOTE_MIN, m_nInstrument))); } return 1; } return 0; } BOOL CViewInstrument::PreTranslateMessage(MSG *pMsg) { if(pMsg) { //We handle keypresses before Windows has a chance to handle them (for alt etc..) if((pMsg->message == WM_SYSKEYUP) || (pMsg->message == WM_KEYUP) || (pMsg->message == WM_SYSKEYDOWN) || (pMsg->message == WM_KEYDOWN)) { CInputHandler *ih = CMainFrame::GetInputHandler(); //Translate message manually UINT nChar = static_cast(pMsg->wParam); UINT nRepCnt = LOWORD(pMsg->lParam); UINT nFlags = HIWORD(pMsg->lParam); KeyEventType kT = ih->GetKeyEventType(nFlags); InputTargetContext ctx = (InputTargetContext)(kCtxViewInstruments); if(ih->KeyEvent(ctx, nChar, nRepCnt, nFlags, kT) != kcNull) return true; // Mapped to a command, no need to pass message on. // Handle Application (menu) key if(pMsg->message == WM_KEYDOWN && nChar == VK_APPS) { CPoint pt(0, 0); if(m_nDragItem > 0) { uint32 point = DragItemToEnvPoint(); pt.SetPoint(PointToScreen(point), ValueToScreen(EnvGetValue(point))); } OnRButtonDown(0, pt); } } } return CModScrollView::PreTranslateMessage(pMsg); } LRESULT CViewInstrument::OnCustomKeyMsg(WPARAM wParam, LPARAM) { CModDoc *pModDoc = GetDocument(); if(!pModDoc) return kcNull; CSoundFile &sndFile = pModDoc->GetSoundFile(); switch(wParam) { case kcPrevInstrument: OnPrevInstrument(); return wParam; case kcNextInstrument: OnNextInstrument(); return wParam; case kcEditCopy: OnEditCopy(); return wParam; case kcEditPaste: OnEditPaste(); return wParam; case kcEditUndo: OnEditUndo(); return wParam; case kcEditRedo: OnEditRedo(); return wParam; case kcNoteOff: PlayNote(NOTE_KEYOFF); return wParam; case kcNoteCut: PlayNote(NOTE_NOTECUT); return wParam; case kcInstrumentLoad: SendCtrlMessage(IDC_INSTRUMENT_OPEN); return wParam; case kcInstrumentSave: SendCtrlMessage(IDC_INSTRUMENT_SAVEAS); return wParam; case kcInstrumentNew: SendCtrlMessage(IDC_INSTRUMENT_NEW); return wParam; // envelope editor case kcInstrumentEnvelopeLoad: OnEnvLoad(); return wParam; case kcInstrumentEnvelopeSave: OnEnvSave(); return wParam; case kcInstrumentEnvelopeZoomIn: OnEnvZoomIn(); return wParam; case kcInstrumentEnvelopeZoomOut: OnEnvZoomOut(); return wParam; case kcInstrumentEnvelopeScale: OnEnvelopeScalePoints(); return wParam; case kcInstrumentEnvelopeSwitchToVolume: OnSelectVolumeEnv(); return wParam; case kcInstrumentEnvelopeSwitchToPanning: OnSelectPanningEnv(); return wParam; case kcInstrumentEnvelopeSwitchToPitch: OnSelectPitchEnv(); return wParam; case kcInstrumentEnvelopeToggleVolume: OnEnvVolChanged(); return wParam; case kcInstrumentEnvelopeTogglePanning: OnEnvPanChanged(); return wParam; case kcInstrumentEnvelopeTogglePitch: OnEnvPitchChanged(); return wParam; case kcInstrumentEnvelopeToggleFilter: OnEnvFilterChanged(); return wParam; case kcInstrumentEnvelopeToggleLoop: OnEnvLoopChanged(); return wParam; case kcInstrumentEnvelopeSelectLoopStart: EnvKbdSelectPoint(ENV_DRAGLOOPSTART); return wParam; case kcInstrumentEnvelopeSelectLoopEnd: EnvKbdSelectPoint(ENV_DRAGLOOPEND); return wParam; case kcInstrumentEnvelopeToggleSustain: OnEnvSustainChanged(); return wParam; case kcInstrumentEnvelopeSelectSustainStart: EnvKbdSelectPoint(ENV_DRAGSUSTAINSTART); return wParam; case kcInstrumentEnvelopeSelectSustainEnd: EnvKbdSelectPoint(ENV_DRAGSUSTAINEND); return wParam; case kcInstrumentEnvelopeToggleCarry: OnEnvCarryChanged(); return wParam; case kcInstrumentEnvelopePointPrev: EnvKbdSelectPoint(ENV_DRAGPREVIOUS); return wParam; case kcInstrumentEnvelopePointNext: EnvKbdSelectPoint(ENV_DRAGNEXT); return wParam; case kcInstrumentEnvelopePointMoveLeft: EnvKbdMovePointLeft(1); return wParam; case kcInstrumentEnvelopePointMoveRight: EnvKbdMovePointRight(1); return wParam; case kcInstrumentEnvelopePointMoveLeftCoarse: EnvKbdMovePointLeft(sndFile.m_PlayState.m_nCurrentRowsPerBeat * sndFile.m_PlayState.m_nMusicSpeed); return wParam; case kcInstrumentEnvelopePointMoveRightCoarse: EnvKbdMovePointRight(sndFile.m_PlayState.m_nCurrentRowsPerBeat * sndFile.m_PlayState.m_nMusicSpeed); return wParam; case kcInstrumentEnvelopePointMoveUp: EnvKbdMovePointVertical(1); return wParam; case kcInstrumentEnvelopePointMoveDown: EnvKbdMovePointVertical(-1); return wParam; case kcInstrumentEnvelopePointMoveUp8: EnvKbdMovePointVertical(8); return wParam; case kcInstrumentEnvelopePointMoveDown8: EnvKbdMovePointVertical(-8); return wParam; case kcInstrumentEnvelopePointInsert: EnvKbdInsertPoint(); return wParam; case kcInstrumentEnvelopePointRemove: EnvKbdRemovePoint(); return wParam; case kcInstrumentEnvelopeSetLoopStart: EnvKbdSetLoopStart(); return wParam; case kcInstrumentEnvelopeSetLoopEnd: EnvKbdSetLoopEnd(); return wParam; case kcInstrumentEnvelopeSetSustainLoopStart: EnvKbdSetSustainStart(); return wParam; case kcInstrumentEnvelopeSetSustainLoopEnd: EnvKbdSetSustainEnd(); return wParam; case kcInstrumentEnvelopeToggleReleaseNode: EnvKbdToggleReleaseNode(); return wParam; } if(wParam >= kcInstrumentStartNotes && wParam <= kcInstrumentEndNotes) { PlayNote(pModDoc->GetNoteWithBaseOctave(static_cast(wParam - kcInstrumentStartNotes), m_nInstrument)); return wParam; } if(wParam >= kcInstrumentStartNoteStops && wParam <= kcInstrumentEndNoteStops) { ModCommand::NOTE note = pModDoc->GetNoteWithBaseOctave(static_cast(wParam - kcInstrumentStartNoteStops), m_nInstrument); if(ModCommand::IsNote(note)) { m_baPlayingNote[note] = false; pModDoc->NoteOff(note, false, m_nInstrument, m_noteChannel[note - NOTE_MIN]); } return wParam; } return kcNull; } void CViewInstrument::OnEnvelopeScalePoints() { CModDoc *pModDoc = GetDocument(); if(pModDoc == nullptr) return; const CSoundFile &sndFile = pModDoc->GetSoundFile(); if(m_nInstrument >= 1 && m_nInstrument <= sndFile.GetNumInstruments() && sndFile.Instruments[m_nInstrument]) { // "Center" y value of the envelope. For panning and pitch, this is 32, for volume and filter it is 0 (minimum). int nOffset = ((m_nEnv != ENV_VOLUME) && !GetEnvelopePtr()->dwFlags[ENV_FILTER]) ? 32 : 0; CScaleEnvPointsDlg dlg(this, *GetEnvelopePtr(), nOffset); if(dlg.DoModal() == IDOK) { PrepareUndo("Scale Envelope"); dlg.Apply(); SetModified(InstrumentHint().Envelope(), true); } } } void CViewInstrument::EnvSetZoom(float newZoom) { m_zoom = Clamp(newZoom, ENV_MIN_ZOOM, ENV_MAX_ZOOM); InvalidateRect(NULL, FALSE); UpdateScrollSize(); UpdateNcButtonState(); } //////////////////////////////////////// // Envelope Editor - Keyboard actions void CViewInstrument::EnvKbdSelectPoint(DragPoints point) { InstrumentEnvelope *pEnv = GetEnvelopePtr(); if(pEnv == nullptr) return; switch(point) { case ENV_DRAGLOOPSTART: case ENV_DRAGLOOPEND: if(!pEnv->dwFlags[ENV_LOOP]) return; m_nDragItem = point; break; case ENV_DRAGSUSTAINSTART: case ENV_DRAGSUSTAINEND: if(!pEnv->dwFlags[ENV_SUSTAIN]) return; m_nDragItem = point; break; case ENV_DRAGPREVIOUS: if(m_nDragItem <= 1 || m_nDragItem > pEnv->size()) m_nDragItem = pEnv->size(); else m_nDragItem--; break; case ENV_DRAGNEXT: if(m_nDragItem >= pEnv->size()) m_nDragItem = 1; else m_nDragItem++; break; } UpdateIndicator(); InvalidateRect(NULL, FALSE); } void CViewInstrument::EnvKbdMovePointLeft(int stepsize) { InstrumentEnvelope *pEnv = GetEnvelopePtr(); if(pEnv == nullptr) return; const MODTYPE modType = GetDocument()->GetModType(); // Move loop points? PrepareUndo("Move Envelope Point"); if(m_nDragItem == ENV_DRAGSUSTAINSTART) { if(pEnv->nSustainStart <= 0) return; pEnv->nSustainStart--; if(modType == MOD_TYPE_XM) pEnv->nSustainEnd = pEnv->nSustainStart; } else if(m_nDragItem == ENV_DRAGSUSTAINEND) { if(pEnv->nSustainEnd <= 0) return; if(pEnv->nSustainEnd <= pEnv->nSustainStart) pEnv->nSustainStart--; pEnv->nSustainEnd--; } else if(m_nDragItem == ENV_DRAGLOOPSTART) { if(pEnv->nLoopStart <= 0) return; pEnv->nLoopStart--; } else if(m_nDragItem == ENV_DRAGLOOPEND) { if(pEnv->nLoopEnd <= 0) return; if(pEnv->nLoopEnd <= pEnv->nLoopStart) pEnv->nLoopStart--; pEnv->nLoopEnd--; } else { // Move envelope node if(!IsDragItemEnvPoint() || m_nDragItem <= 1) { GetDocument()->GetInstrumentUndo().RemoveLastUndoStep(m_nInstrument); return; } if(!EnvSetValue(m_nDragItem - 1, pEnv->at(m_nDragItem - 1).tick - stepsize)) { GetDocument()->GetInstrumentUndo().RemoveLastUndoStep(m_nInstrument); return; } } UpdateIndicator(); SetModified(InstrumentHint().Envelope(), true); } void CViewInstrument::EnvKbdMovePointRight(int stepsize) { InstrumentEnvelope *pEnv = GetEnvelopePtr(); if(pEnv == nullptr) return; const MODTYPE modType = GetDocument()->GetModType(); // Move loop points? PrepareUndo("Move Envelope Point"); if(m_nDragItem == ENV_DRAGSUSTAINSTART) { if(pEnv->nSustainStart >= pEnv->size() - 1) return; if(pEnv->nSustainStart >= pEnv->nSustainEnd) pEnv->nSustainEnd++; pEnv->nSustainStart++; } else if(m_nDragItem == ENV_DRAGSUSTAINEND) { if(pEnv->nSustainEnd >= pEnv->size() - 1) return; pEnv->nSustainEnd++; if(modType == MOD_TYPE_XM) pEnv->nSustainStart = pEnv->nSustainEnd; } else if(m_nDragItem == ENV_DRAGLOOPSTART) { if(pEnv->nLoopStart >= pEnv->size() - 1) return; if(pEnv->nLoopStart >= pEnv->nLoopEnd) pEnv->nLoopEnd++; pEnv->nLoopStart++; } else if(m_nDragItem == ENV_DRAGLOOPEND) { if(pEnv->nLoopEnd >= pEnv->size() - 1) return; pEnv->nLoopEnd++; } else { // Move envelope node if(!IsDragItemEnvPoint() || m_nDragItem <= 1) { GetDocument()->GetInstrumentUndo().RemoveLastUndoStep(m_nInstrument); return; } if(!EnvSetValue(m_nDragItem - 1, pEnv->at(m_nDragItem - 1).tick + stepsize)) { GetDocument()->GetInstrumentUndo().RemoveLastUndoStep(m_nInstrument); return; } } UpdateIndicator(); SetModified(InstrumentHint().Envelope(), true); } void CViewInstrument::EnvKbdMovePointVertical(int stepsize) { InstrumentEnvelope *pEnv = GetEnvelopePtr(); if(pEnv == nullptr || !IsDragItemEnvPoint()) return; int val = pEnv->at(m_nDragItem - 1).value + stepsize; PrepareUndo("Move Envelope Point"); if(EnvSetValue(m_nDragItem - 1, int32_min, val, false)) { UpdateIndicator(); SetModified(InstrumentHint().Envelope(), true); } else { GetDocument()->GetInstrumentUndo().RemoveLastUndoStep(m_nInstrument); } } void CViewInstrument::EnvKbdInsertPoint() { InstrumentEnvelope *pEnv = GetEnvelopePtr(); if(pEnv == nullptr) return; if(!IsDragItemEnvPoint()) m_nDragItem = pEnv->size(); EnvelopeNode::tick_t newTick = 10; EnvelopeNode::value_t newVal = m_nEnv == ENV_VOLUME ? ENVELOPE_MAX : ENVELOPE_MID; if(m_nDragItem < pEnv->size() && (pEnv->at(m_nDragItem).tick - pEnv->at(m_nDragItem - 1).tick > 1)) { // If some other point than the last is selected: interpolate between this and next point (if there's room between them) newTick = (pEnv->at(m_nDragItem - 1).tick + pEnv->at(m_nDragItem).tick) / 2; newVal = (pEnv->at(m_nDragItem - 1).value + pEnv->at(m_nDragItem).value) / 2; } else if(!pEnv->empty()) { // Last point is selected: add point after last point newTick = pEnv->back().tick + 4; newVal = pEnv->back().value; } auto newPoint = EnvInsertPoint(newTick, newVal); if(newPoint > 0) m_nDragItem = newPoint; UpdateIndicator(); } void CViewInstrument::EnvKbdRemovePoint() { InstrumentEnvelope *pEnv = GetEnvelopePtr(); if(pEnv == nullptr || !IsDragItemEnvPoint() || pEnv->empty()) return; if(m_nDragItem > pEnv->size()) m_nDragItem = pEnv->size(); EnvRemovePoint(m_nDragItem - 1); UpdateIndicator(); } void CViewInstrument::EnvKbdSetLoopStart() { InstrumentEnvelope *pEnv = GetEnvelopePtr(); if(pEnv == nullptr || !IsDragItemEnvPoint()) return; PrepareUndo("Set Envelope Loop Start"); if(!EnvGetLoop()) EnvSetLoopStart(0); EnvSetLoopStart(m_nDragItem - 1); SetModified(InstrumentHint(m_nInstrument).Envelope(), true); } void CViewInstrument::EnvKbdSetLoopEnd() { InstrumentEnvelope *pEnv = GetEnvelopePtr(); if(pEnv == nullptr || !IsDragItemEnvPoint()) return; PrepareUndo("Set Envelope Loop End"); if(!EnvGetLoop()) { EnvSetLoop(true); EnvSetLoopStart(0); } EnvSetLoopEnd(m_nDragItem - 1); SetModified(InstrumentHint(m_nInstrument).Envelope(), true); } void CViewInstrument::EnvKbdSetSustainStart() { InstrumentEnvelope *pEnv = GetEnvelopePtr(); if(pEnv == nullptr || !IsDragItemEnvPoint()) return; PrepareUndo("Set Envelope Sustain Start"); if(!EnvGetSustain()) EnvSetSustain(true); EnvSetSustainStart(m_nDragItem - 1); SetModified(InstrumentHint(m_nInstrument).Envelope(), true); } void CViewInstrument::EnvKbdSetSustainEnd() { InstrumentEnvelope *pEnv = GetEnvelopePtr(); if(pEnv == nullptr || !IsDragItemEnvPoint()) return; PrepareUndo("Set Envelope Sustain End"); if(!EnvGetSustain()) { EnvSetSustain(true); EnvSetSustainStart(0); } EnvSetSustainEnd(m_nDragItem - 1); SetModified(InstrumentHint(m_nInstrument).Envelope(), true); } void CViewInstrument::EnvKbdToggleReleaseNode() { InstrumentEnvelope *pEnv = GetEnvelopePtr(); if(pEnv == nullptr || !IsDragItemEnvPoint()) return; PrepareUndo("Toggle Release Node"); if(EnvToggleReleaseNode(m_nDragItem - 1)) { UpdateIndicator(); SetModified(InstrumentHint().Envelope(), true); } else { GetDocument()->GetInstrumentUndo().RemoveLastUndoStep(m_nInstrument); } } // Get a pointer to the currently active instrument. ModInstrument *CViewInstrument::GetInstrumentPtr() const { CModDoc *pModDoc = GetDocument(); if(pModDoc == nullptr) return nullptr; return pModDoc->GetSoundFile().Instruments[m_nInstrument]; } // Get a pointer to the currently selected envelope. // This function also implicitely validates the moddoc and soundfile pointers. InstrumentEnvelope *CViewInstrument::GetEnvelopePtr() const { // First do some standard checks... ModInstrument *pIns = GetInstrumentPtr(); if(pIns == nullptr) return nullptr; return &pIns->GetEnvelope(m_nEnv); } bool CViewInstrument::CanMovePoint(uint32 envPoint, int step) { const InstrumentEnvelope *env = GetEnvelopePtr(); if(env == nullptr) return false; // Can't move first point if(envPoint == 0) { return false; } // Can't move left of previous point if((step < 0) && (env->at(envPoint).tick - env->at(envPoint - 1).tick <= -step)) { return false; } // Can't move right of next point if((step > 0) && (envPoint < env->size() - 1) && (env->at(envPoint + 1).tick - env->at(envPoint).tick <= step)) { return false; } return true; } BOOL CViewInstrument::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt) { // Ctrl + mouse wheel: envelope zoom. if(nFlags == MK_CONTROL) { // Speed up zoom scrolling by some factor (might need some tuning). const float speedUpFactor = std::max(1.0f, m_zoom * 7.0f / ENV_MAX_ZOOM); EnvSetZoom(m_zoom + speedUpFactor * (zDelta / WHEEL_DELTA)); } return CModScrollView::OnMouseWheel(nFlags, zDelta, pt); } void CViewInstrument::OnXButtonUp(UINT nFlags, UINT nButton, CPoint point) { if(nButton == XBUTTON1) OnPrevInstrument(); else if(nButton == XBUTTON2) OnNextInstrument(); CModScrollView::OnXButtonUp(nFlags, nButton, point); } void CViewInstrument::OnEnvLoad() { if(GetInstrumentPtr() == nullptr) return; FileDialog dlg = OpenFileDialog() .DefaultExtension("envelope") .ExtensionFilter("Instrument Envelopes (*.envelope)|*.envelope||") .WorkingDirectory(TrackerSettings::Instance().PathInstruments.GetWorkingDir()); if(!dlg.Show(this)) return; TrackerSettings::Instance().PathInstruments.SetWorkingDir(dlg.GetWorkingDirectory()); PrepareUndo("Replace Envelope"); if(GetDocument()->LoadEnvelope(m_nInstrument, m_nEnv, dlg.GetFirstFile())) { SetModified(InstrumentHint(m_nInstrument).Envelope(), true); } else { GetDocument()->GetInstrumentUndo().RemoveLastUndoStep(m_nInstrument); } } void CViewInstrument::OnEnvSave() { const InstrumentEnvelope *env = GetEnvelopePtr(); if(env == nullptr || env->empty()) { MessageBeep(MB_ICONWARNING); return; } FileDialog dlg = SaveFileDialog() .DefaultExtension("envelope") .ExtensionFilter("Instrument Envelopes (*.envelope)|*.envelope||") .WorkingDirectory(TrackerSettings::Instance().PathInstruments.GetWorkingDir()); if(!dlg.Show(this)) return; TrackerSettings::Instance().PathInstruments.SetWorkingDir(dlg.GetWorkingDirectory()); if(!GetDocument()->SaveEnvelope(m_nInstrument, m_nEnv, dlg.GetFirstFile())) { Reporting::Error(MPT_CFORMAT("Unable to save file {}")(dlg.GetFirstFile()), _T("OpenMPT"), this); } } void CViewInstrument::OnUpdateUndo(CCmdUI *pCmdUI) { CModDoc *pModDoc = GetDocument(); if((pCmdUI) && (pModDoc)) { pCmdUI->Enable(pModDoc->GetInstrumentUndo().CanUndo(m_nInstrument)); pCmdUI->SetText(CMainFrame::GetInputHandler()->GetKeyTextFromCommand(kcEditUndo, _T("Undo ") + mpt::ToCString(pModDoc->GetSoundFile().GetCharsetInternal(), pModDoc->GetInstrumentUndo().GetUndoName(m_nInstrument)))); } } void CViewInstrument::OnUpdateRedo(CCmdUI *pCmdUI) { CModDoc *pModDoc = GetDocument(); if((pCmdUI) && (pModDoc)) { pCmdUI->Enable(pModDoc->GetInstrumentUndo().CanRedo(m_nInstrument)); pCmdUI->SetText(CMainFrame::GetInputHandler()->GetKeyTextFromCommand(kcEditRedo, _T("Redo ") + mpt::ToCString(pModDoc->GetSoundFile().GetCharsetInternal(), pModDoc->GetInstrumentUndo().GetRedoName(m_nInstrument)))); } } void CViewInstrument::OnEditUndo() { CModDoc *pModDoc = GetDocument(); if(pModDoc == nullptr) return; if(pModDoc->GetInstrumentUndo().Undo(m_nInstrument)) { SetModified(InstrumentHint().Info().Envelope().Names(), true); } } void CViewInstrument::OnEditRedo() { CModDoc *pModDoc = GetDocument(); if(pModDoc == nullptr) return; if(pModDoc->GetInstrumentUndo().Redo(m_nInstrument)) { SetModified(InstrumentHint().Info().Envelope().Names(), true); } } INT_PTR CViewInstrument::OnToolHitTest(CPoint point, TOOLINFO *pTI) const { CRect ncRect; ClientToScreen(&point); const auto ncButton = GetNcButtonAtPoint(point, &ncRect); if(ncButton == uint32_max) return CModScrollView::OnToolHitTest(point, pTI); auto buttonID = cLeftBarButtons[ncButton]; ScreenToClient(&ncRect); pTI->hwnd = m_hWnd; pTI->uId = buttonID; pTI->rect = ncRect; CString text = LoadResourceString(buttonID); CommandID cmd = kcNull; switch(buttonID) { case ID_ENVSEL_VOLUME: cmd = kcInstrumentEnvelopeSwitchToVolume; break; case ID_ENVSEL_PANNING: cmd = kcInstrumentEnvelopeSwitchToPanning; break; case ID_ENVSEL_PITCH: cmd = kcInstrumentEnvelopeSwitchToPitch; break; case ID_ENVELOPE_VOLUME: cmd = kcInstrumentEnvelopeToggleVolume; break; case ID_ENVELOPE_PANNING: cmd = kcInstrumentEnvelopeTogglePanning; break; case ID_ENVELOPE_PITCH: cmd = kcInstrumentEnvelopeTogglePitch; break; case ID_ENVELOPE_FILTER: cmd = kcInstrumentEnvelopeToggleFilter; break; case ID_ENVELOPE_SETLOOP: cmd = kcInstrumentEnvelopeToggleLoop; break; case ID_ENVELOPE_SUSTAIN: cmd = kcInstrumentEnvelopeToggleSustain; break; case ID_ENVELOPE_CARRY: cmd = kcInstrumentEnvelopeToggleCarry; break; case ID_INSTRUMENT_SAMPLEMAP: cmd = kcInsNoteMapEditSampleMap; break; case ID_ENVELOPE_ZOOM_IN: cmd = kcInstrumentEnvelopeZoomIn; break; case ID_ENVELOPE_ZOOM_OUT: cmd = kcInstrumentEnvelopeZoomOut; break; case ID_ENVELOPE_LOAD: cmd = kcInstrumentEnvelopeLoad; break; case ID_ENVELOPE_SAVE: cmd = kcInstrumentEnvelopeSave; break; } if(cmd != kcNull) { auto keyText = CMainFrame::GetInputHandler()->m_activeCommandSet->GetKeyTextFromCommand(cmd, 0); if(!keyText.IsEmpty()) text += MPT_CFORMAT(" ({})")(keyText); } // MFC will free() the text auto size = text.GetLength() + 1; TCHAR *textP = static_cast(calloc(size, sizeof(TCHAR))); std::copy(text.GetString(), text.GetString() + size, textP); pTI->lpszText = textP; return buttonID; } // Accessible description for screen readers HRESULT CViewInstrument::get_accName(VARIANT varChild, BSTR *pszName) { const InstrumentEnvelope *env = GetEnvelopePtr(); if(env == nullptr) return CModScrollView::get_accName(varChild, pszName); const TCHAR *typeStr = _T(""); switch(m_nEnv) { case ENV_VOLUME: typeStr = _T("Volume"); break; case ENV_PANNING: typeStr = _T("Panning"); break; case ENV_PITCH: typeStr = env->dwFlags[ENV_FILTER] ? _T("Filter") : _T("Pitch"); break; } CString str; if(env->empty() || m_nDragItem == 0) { str = typeStr; if(env->empty()) str += _T(" envelope has no points"); else str += MPT_CFORMAT(" envelope, {} point{}")(env->size(), env->size() == 1 ? CString(_T("")) : CString(_T("s"))); } else { bool isEnvPoint = false; auto point = DragItemToEnvPoint(); auto tick = EnvGetTick(point); switch(m_nDragItem) { case ENV_DRAGLOOPSTART: str = _T("Loop start"); break; case ENV_DRAGLOOPEND: str = _T("Loop end"); break; case ENV_DRAGSUSTAINSTART: str = _T("Sustain loop start"); break; case ENV_DRAGSUSTAINEND: str = _T("Sustain loop end"); break; default: isEnvPoint = true; } if(!isEnvPoint) { str += MPT_CFORMAT(" at point {}, tick {}")(point + 1, tick); } else { str = MPT_CFORMAT("Point {}, tick {}, {} {}")(point + 1, tick, CString(typeStr), EnvValueToString(EnvGetTick(point), EnvGetValue(point))); if(env->dwFlags[ENV_LOOP]) { if(point == env->nLoopStart) str += _T(", loop start"); if(point == env->nLoopEnd) str += _T(", loop end"); } if(env->dwFlags[ENV_SUSTAIN]) { if(point == env->nLoopStart) str += _T(", sustain loop start"); if(point == env->nLoopEnd) str += _T(", sustain loop end"); } if(env->nReleaseNode == point) str += _T(", release node"); } } *pszName = str.AllocSysString(); return S_OK; } OPENMPT_NAMESPACE_END