winamp/Src/external_dependencies/openmpt-trunk/mptrack/View_ins.cpp
2024-09-24 14:54:57 +02:00

2955 lines
80 KiB
C++

/*
* 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<int>((maxTick + 2) * m_zoom);
sizeTotal.cy = 1;
sizeLine.cx = mpt::saturate_round<int>(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<decltype(maxtick)>::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<EnvelopeNode::tick_t>(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<EnvelopeNode::value_t>(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<EnvelopeNode::tick_t>(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_cast<decltype(envelope->nLoopStart)>(nPoint);
if(envelope->nLoopEnd < nPoint)
envelope->nLoopEnd = static_cast<decltype(envelope->nLoopEnd)>(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_cast<decltype(envelope->nLoopEnd)>(nPoint);
if(envelope->nLoopStart > nPoint)
envelope->nLoopStart = static_cast<decltype(envelope->nLoopStart)>(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_cast<decltype(envelope->nSustainStart)>(nPoint);
if((envelope->nSustainEnd < nPoint) || (sndFile.GetType() & MOD_TYPE_XM))
envelope->nSustainEnd = static_cast<decltype(envelope->nSustainEnd)>(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_cast<decltype(envelope->nSustainEnd)>(nPoint);
if((envelope->nSustainStart > nPoint) || (sndFile.GetType() & MOD_TYPE_XM))
envelope->nSustainStart = static_cast<decltype(envelope->nSustainStart)>(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_cast<decltype(envelope->nReleaseNode)>(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<EnvelopeFlags> 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<int>((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<int>((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_LEFTBAR_BUTTONS; i++) if (cLeftBarButtons[i] != ID_SEPARATOR)
{
DWORD dwStyle = 0;
switch(cLeftBarButtons[i])
{
case ID_ENVSEL_VOLUME: if (m_nEnv == ENV_VOLUME) dwStyle |= NCBTNS_CHECKED; break;
case ID_ENVSEL_PANNING: if (m_nEnv == ENV_PANNING) dwStyle |= NCBTNS_CHECKED; break;
case ID_ENVSEL_PITCH: if (!(sndFile.GetType() & (MOD_TYPE_IT|MOD_TYPE_MPT))) dwStyle |= NCBTNS_DISABLED;
else if (m_nEnv == ENV_PITCH) dwStyle |= NCBTNS_CHECKED; break;
case ID_ENVELOPE_SETLOOP: if (EnvGetLoop()) dwStyle |= NCBTNS_CHECKED; break;
case ID_ENVELOPE_SUSTAIN: if (EnvGetSustain()) dwStyle |= NCBTNS_CHECKED; break;
case ID_ENVELOPE_CARRY: if (!(sndFile.GetType() & (MOD_TYPE_IT|MOD_TYPE_MPT))) dwStyle |= NCBTNS_DISABLED;
else if (EnvGetCarry()) dwStyle |= NCBTNS_CHECKED; break;
case ID_ENVELOPE_VOLUME: if (EnvGetVolEnv()) dwStyle |= NCBTNS_CHECKED; break;
case ID_ENVELOPE_PANNING: if (EnvGetPanEnv()) dwStyle |= NCBTNS_CHECKED; break;
case ID_ENVELOPE_PITCH: if (!(sndFile.GetType() & (MOD_TYPE_IT|MOD_TYPE_MPT))) dwStyle |= NCBTNS_DISABLED; else
if (EnvGetPitchEnv()) dwStyle |= NCBTNS_CHECKED; break;
case ID_ENVELOPE_FILTER: if (!(sndFile.GetType() & (MOD_TYPE_IT|MOD_TYPE_MPT))) dwStyle |= NCBTNS_DISABLED; else
if (EnvGetFilterEnv()) dwStyle |= NCBTNS_CHECKED; break;
case ID_ENVELOPE_VIEWGRID: if (m_bGrid) dwStyle |= NCBTNS_CHECKED; break;
case ID_ENVELOPE_ZOOM_IN: if (m_zoom >= 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<InstrumentHint>();
FlagSet<HintType> 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<EnvelopeNode::tick_t>(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<EnvelopeNode::tick_t>(nTick), static_cast<EnvelopeNode::value_t>(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<int>((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_cast<decltype(pEnv->nLoopEnd)>(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_cast<decltype(pEnv->nSustainEnd)>(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<TCHAR> 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<LPARAM>(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<uint32>(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<UINT>(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<int>(wParam - kcInstrumentStartNotes), m_nInstrument));
return wParam;
}
if(wParam >= kcInstrumentStartNoteStops && wParam <= kcInstrumentEndNoteStops)
{
ModCommand::NOTE note = pModDoc->GetNoteWithBaseOctave(static_cast<int>(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<TCHAR *>(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