3214 lines
91 KiB
C++
3214 lines
91 KiB
C++
|
/*
|
||
|
* Ctrl_ins.cpp
|
||
|
* ------------
|
||
|
* Purpose: Instrument tab, upper 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 "Childfrm.h"
|
||
|
#include "ImageLists.h"
|
||
|
#include "Moddoc.h"
|
||
|
#include "../soundlib/mod_specifications.h"
|
||
|
#include "../soundlib/plugins/PlugInterface.h"
|
||
|
#include "Globals.h"
|
||
|
#include "Ctrl_ins.h"
|
||
|
#include "View_ins.h"
|
||
|
#include "dlg_misc.h"
|
||
|
#include "TuningDialog.h"
|
||
|
#include "../common/misc_util.h"
|
||
|
#include "../common/mptStringBuffer.h"
|
||
|
#include "SelectPluginDialog.h"
|
||
|
#include "../common/mptFileIO.h"
|
||
|
#include "../common/FileReader.h"
|
||
|
#include "FileDialog.h"
|
||
|
|
||
|
|
||
|
OPENMPT_NAMESPACE_BEGIN
|
||
|
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////
|
||
|
// CNoteMapWnd
|
||
|
|
||
|
BEGIN_MESSAGE_MAP(CNoteMapWnd, CStatic)
|
||
|
ON_WM_ERASEBKGND()
|
||
|
ON_WM_PAINT()
|
||
|
ON_WM_SETFOCUS()
|
||
|
ON_WM_KILLFOCUS()
|
||
|
ON_WM_LBUTTONDOWN()
|
||
|
ON_WM_MBUTTONDOWN()
|
||
|
ON_WM_RBUTTONDOWN()
|
||
|
ON_WM_LBUTTONDBLCLK()
|
||
|
ON_WM_MOUSEWHEEL()
|
||
|
ON_COMMAND(ID_NOTEMAP_TRANS_UP, &CNoteMapWnd::OnMapTransposeUp)
|
||
|
ON_COMMAND(ID_NOTEMAP_TRANS_DOWN, &CNoteMapWnd::OnMapTransposeDown)
|
||
|
ON_COMMAND(ID_NOTEMAP_COPY_NOTE, &CNoteMapWnd::OnMapCopyNote)
|
||
|
ON_COMMAND(ID_NOTEMAP_COPY_SMP, &CNoteMapWnd::OnMapCopySample)
|
||
|
ON_COMMAND(ID_NOTEMAP_RESET, &CNoteMapWnd::OnMapReset)
|
||
|
ON_COMMAND(ID_NOTEMAP_TRANSPOSE_SAMPLES, &CNoteMapWnd::OnTransposeSamples)
|
||
|
ON_COMMAND(ID_NOTEMAP_REMOVE, &CNoteMapWnd::OnMapRemove)
|
||
|
ON_COMMAND(ID_INSTRUMENT_SAMPLEMAP, &CNoteMapWnd::OnEditSampleMap)
|
||
|
ON_COMMAND(ID_INSTRUMENT_DUPLICATE, &CNoteMapWnd::OnInstrumentDuplicate)
|
||
|
ON_MESSAGE(WM_MOD_KEYCOMMAND, &CNoteMapWnd::OnCustomKeyMsg)
|
||
|
ON_COMMAND_RANGE(ID_NOTEMAP_EDITSAMPLE, ID_NOTEMAP_EDITSAMPLE + MAX_SAMPLES, &CNoteMapWnd::OnEditSample)
|
||
|
END_MESSAGE_MAP()
|
||
|
|
||
|
|
||
|
BOOL CNoteMapWnd::PreTranslateMessage(MSG* pMsg)
|
||
|
{
|
||
|
if(!pMsg)
|
||
|
return TRUE;
|
||
|
uint32 wParam = static_cast<uint32>(pMsg->wParam);
|
||
|
|
||
|
{
|
||
|
//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 = wParam;
|
||
|
UINT nRepCnt = LOWORD(pMsg->lParam);
|
||
|
UINT nFlags = HIWORD(pMsg->lParam);
|
||
|
KeyEventType kT = ih->GetKeyEventType(nFlags);
|
||
|
InputTargetContext ctx = (InputTargetContext)(kCtxInsNoteMap);
|
||
|
|
||
|
if (ih->KeyEvent(ctx, nChar, nRepCnt, nFlags, kT) != kcNull)
|
||
|
return true; // Mapped to a command, no need to pass message on.
|
||
|
|
||
|
// a bit of a hack...
|
||
|
ctx = (InputTargetContext)(kCtxCtrlInstruments);
|
||
|
|
||
|
if (ih->KeyEvent(ctx, nChar, nRepCnt, nFlags, kT) != kcNull)
|
||
|
return true; // Mapped to a command, no need to pass message on.
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//The key was not handled by a command, but it might still be useful
|
||
|
if (pMsg->message == WM_CHAR) //key is a character
|
||
|
{
|
||
|
UINT nFlags = HIWORD(pMsg->lParam);
|
||
|
KeyEventType kT = CMainFrame::GetInputHandler()->GetKeyEventType(nFlags);
|
||
|
|
||
|
if (kT == kKeyEventDown)
|
||
|
if (HandleChar(wParam))
|
||
|
return true;
|
||
|
}
|
||
|
else if (pMsg->message == WM_KEYDOWN) //key is not a character
|
||
|
{
|
||
|
if(HandleNav(wParam))
|
||
|
return true;
|
||
|
|
||
|
// Handle Application (menu) key
|
||
|
if(wParam == VK_APPS)
|
||
|
{
|
||
|
CRect clientRect;
|
||
|
GetClientRect(clientRect);
|
||
|
clientRect.bottom = clientRect.top + mpt::align_up(clientRect.Height(), m_cyFont);
|
||
|
OnRButtonDown(0, clientRect.CenterPoint());
|
||
|
}
|
||
|
}
|
||
|
else if (pMsg->message == WM_KEYUP) //stop notes on key release
|
||
|
{
|
||
|
if (((pMsg->wParam >= '0') && (pMsg->wParam <= '9')) || (pMsg->wParam == ' ') ||
|
||
|
((pMsg->wParam >= VK_NUMPAD0) && (pMsg->wParam <= VK_NUMPAD9)))
|
||
|
{
|
||
|
StopNote();
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return CStatic::PreTranslateMessage(pMsg);
|
||
|
}
|
||
|
|
||
|
|
||
|
void CNoteMapWnd::PrepareUndo(const char *description)
|
||
|
{
|
||
|
m_modDoc.GetInstrumentUndo().PrepareUndo(m_nInstrument, description);
|
||
|
}
|
||
|
|
||
|
|
||
|
void CNoteMapWnd::SetCurrentInstrument(INSTRUMENTINDEX nIns)
|
||
|
{
|
||
|
if (nIns != m_nInstrument)
|
||
|
{
|
||
|
if (nIns < MAX_INSTRUMENTS) m_nInstrument = nIns;
|
||
|
|
||
|
// create missing instrument if needed
|
||
|
CSoundFile &sndFile = m_modDoc.GetSoundFile();
|
||
|
if(m_nInstrument > 0 && m_nInstrument <= sndFile.GetNumInstruments() && sndFile.Instruments[m_nInstrument] == nullptr)
|
||
|
{
|
||
|
ModInstrument *instrument = sndFile.AllocateInstrument(m_nInstrument);
|
||
|
if(instrument == nullptr)
|
||
|
return;
|
||
|
m_modDoc.InitializeInstrument(instrument);
|
||
|
}
|
||
|
|
||
|
Invalidate(FALSE);
|
||
|
UpdateAccessibleTitle();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CNoteMapWnd::SetCurrentNote(UINT nNote)
|
||
|
{
|
||
|
if(nNote != m_nNote && ModCommand::IsNote(static_cast<ModCommand::NOTE>(nNote + NOTE_MIN)))
|
||
|
{
|
||
|
m_nNote = nNote;
|
||
|
Invalidate(FALSE);
|
||
|
UpdateAccessibleTitle();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CNoteMapWnd::OnPaint()
|
||
|
{
|
||
|
CPaintDC dc(this);
|
||
|
|
||
|
CRect rcClient;
|
||
|
GetClientRect(&rcClient);
|
||
|
const auto highlightBrush = GetSysColorBrush(COLOR_HIGHLIGHT), windowBrush = GetSysColorBrush(COLOR_WINDOW);
|
||
|
const auto colorText = GetSysColor(COLOR_WINDOWTEXT);
|
||
|
const auto colorTextSel = GetSysColor(COLOR_HIGHLIGHTTEXT);
|
||
|
auto oldFont = dc.SelectObject(CMainFrame::GetGUIFont());
|
||
|
dc.SetBkMode(TRANSPARENT);
|
||
|
if ((m_cxFont <= 0) || (m_cyFont <= 0))
|
||
|
{
|
||
|
CSize sz;
|
||
|
sz = dc.GetTextExtent(_T("C#0."), 4);
|
||
|
m_cyFont = sz.cy + 2;
|
||
|
m_cxFont = rcClient.right / 3;
|
||
|
}
|
||
|
dc.IntersectClipRect(&rcClient);
|
||
|
|
||
|
const CSoundFile &sndFile = m_modDoc.GetSoundFile();
|
||
|
if (m_cxFont > 0 && m_cyFont > 0)
|
||
|
{
|
||
|
const bool focus = (::GetFocus() == m_hWnd);
|
||
|
const ModInstrument *pIns = sndFile.Instruments[m_nInstrument];
|
||
|
CRect rect;
|
||
|
|
||
|
int nNotes = (rcClient.bottom + m_cyFont - 1) / m_cyFont;
|
||
|
int nPos = m_nNote - (nNotes/2);
|
||
|
int ypaint = 0;
|
||
|
mpt::winstring s;
|
||
|
for (int ynote=0; ynote<nNotes; ynote++, ypaint+=m_cyFont, nPos++)
|
||
|
{
|
||
|
// Note
|
||
|
bool isValidPos = (nPos >= 0) && (nPos < NOTE_MAX - NOTE_MIN + 1);
|
||
|
if (isValidPos)
|
||
|
{
|
||
|
s = mpt::ToWin(sndFile.GetNoteName(static_cast<ModCommand::NOTE>(nPos + 1), m_nInstrument));
|
||
|
s.resize(4);
|
||
|
} else
|
||
|
{
|
||
|
s.clear();
|
||
|
}
|
||
|
rect.SetRect(0, ypaint, m_cxFont, ypaint+m_cyFont);
|
||
|
DrawButtonRect(dc, &rect, s.c_str(), FALSE, FALSE);
|
||
|
// Mapped Note
|
||
|
bool highlight = ((focus) && (nPos == (int)m_nNote));
|
||
|
rect.left = rect.right;
|
||
|
rect.right = m_cxFont*2-1;
|
||
|
s = _T("...");
|
||
|
if(pIns != nullptr && isValidPos && (pIns->NoteMap[nPos] != NOTE_NONE))
|
||
|
{
|
||
|
ModCommand::NOTE n = pIns->NoteMap[nPos];
|
||
|
if(ModCommand::IsNote(n))
|
||
|
{
|
||
|
s = mpt::ToWin(sndFile.GetNoteName(n, m_nInstrument));
|
||
|
s.resize(4);
|
||
|
} else
|
||
|
{
|
||
|
s = _T("???");
|
||
|
}
|
||
|
}
|
||
|
FillRect(dc, &rect, highlight ? highlightBrush : windowBrush);
|
||
|
if(nPos == (int)m_nNote && !m_bIns)
|
||
|
{
|
||
|
rect.InflateRect(-1, -1);
|
||
|
dc.DrawFocusRect(&rect);
|
||
|
rect.InflateRect(1, 1);
|
||
|
}
|
||
|
dc.SetTextColor(highlight ? colorTextSel : colorText);
|
||
|
dc.DrawText(s.c_str(), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER | DT_NOPREFIX);
|
||
|
// Sample
|
||
|
highlight = (focus && nPos == (int)m_nNote);
|
||
|
rect.left = rcClient.left + m_cxFont * 2 + 3;
|
||
|
rect.right = rcClient.right;
|
||
|
s = _T(" ..");
|
||
|
if(pIns && nPos >= 0 && nPos < NOTE_MAX && pIns->Keyboard[nPos])
|
||
|
{
|
||
|
s = mpt::tfmt::right(3, mpt::tfmt::dec(pIns->Keyboard[nPos]));
|
||
|
}
|
||
|
FillRect(dc, &rect, highlight ? highlightBrush : windowBrush);
|
||
|
if((nPos == (int)m_nNote) && (m_bIns))
|
||
|
{
|
||
|
rect.InflateRect(-1, -1);
|
||
|
dc.DrawFocusRect(&rect);
|
||
|
rect.InflateRect(1, 1);
|
||
|
}
|
||
|
dc.SetTextColor((highlight) ? colorTextSel : colorText);
|
||
|
dc.DrawText(s.c_str(), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER | DT_NOPREFIX);
|
||
|
}
|
||
|
rect.SetRect(rcClient.left + m_cxFont * 2 - 1, rcClient.top, rcClient.left + m_cxFont * 2 + 3, ypaint);
|
||
|
DrawButtonRect(dc, &rect, _T(""), FALSE, FALSE);
|
||
|
if (ypaint < rcClient.bottom)
|
||
|
{
|
||
|
rect.SetRect(rcClient.left, ypaint, rcClient.right, rcClient.bottom);
|
||
|
FillRect(dc, &rect, GetSysColorBrush(COLOR_BTNFACE));
|
||
|
}
|
||
|
}
|
||
|
dc.SelectObject(oldFont);
|
||
|
}
|
||
|
|
||
|
|
||
|
void CNoteMapWnd::OnSetFocus(CWnd *pOldWnd)
|
||
|
{
|
||
|
CStatic::OnSetFocus(pOldWnd);
|
||
|
Invalidate(FALSE);
|
||
|
CMainFrame::GetMainFrame()->m_pNoteMapHasFocus = this;
|
||
|
m_undo = true;
|
||
|
}
|
||
|
|
||
|
|
||
|
void CNoteMapWnd::OnKillFocus(CWnd *pNewWnd)
|
||
|
{
|
||
|
CStatic::OnKillFocus(pNewWnd);
|
||
|
Invalidate(FALSE);
|
||
|
CMainFrame::GetMainFrame()->m_pNoteMapHasFocus = nullptr;
|
||
|
}
|
||
|
|
||
|
|
||
|
void CNoteMapWnd::OnLButtonDown(UINT, CPoint pt)
|
||
|
{
|
||
|
if ((pt.x >= m_cxFont) && (pt.x < m_cxFont*2) && (m_bIns))
|
||
|
{
|
||
|
m_bIns = false;
|
||
|
Invalidate(FALSE);
|
||
|
}
|
||
|
if ((pt.x > m_cxFont*2) && (pt.x <= m_cxFont*3) && (!m_bIns))
|
||
|
{
|
||
|
m_bIns = true;
|
||
|
Invalidate(FALSE);
|
||
|
}
|
||
|
if ((pt.x >= 0) && (m_cyFont))
|
||
|
{
|
||
|
CRect rcClient;
|
||
|
GetClientRect(&rcClient);
|
||
|
int nNotes = (rcClient.bottom + m_cyFont - 1) / m_cyFont;
|
||
|
int n = (pt.y / m_cyFont) + m_nNote - (nNotes/2);
|
||
|
if(n >= 0)
|
||
|
{
|
||
|
SetCurrentNote(n);
|
||
|
}
|
||
|
}
|
||
|
SetFocus();
|
||
|
}
|
||
|
|
||
|
|
||
|
void CNoteMapWnd::OnLButtonDblClk(UINT, CPoint)
|
||
|
{
|
||
|
// Double-click edits sample map
|
||
|
OnEditSampleMap();
|
||
|
}
|
||
|
|
||
|
|
||
|
void CNoteMapWnd::OnRButtonDown(UINT, CPoint pt)
|
||
|
{
|
||
|
CInputHandler* ih = CMainFrame::GetInputHandler();
|
||
|
|
||
|
CSoundFile &sndFile = m_modDoc.GetSoundFile();
|
||
|
ModInstrument *pIns = sndFile.Instruments[m_nInstrument];
|
||
|
if (pIns)
|
||
|
{
|
||
|
HMENU hMenu = ::CreatePopupMenu();
|
||
|
HMENU hSubMenu = ::CreatePopupMenu();
|
||
|
|
||
|
if (hMenu)
|
||
|
{
|
||
|
AppendMenu(hMenu, MF_STRING, ID_INSTRUMENT_SAMPLEMAP, ih->GetKeyTextFromCommand(kcInsNoteMapEditSampleMap, _T("Edit Sample &Map")));
|
||
|
if (hSubMenu)
|
||
|
{
|
||
|
// Create sub menu with a list of all samples that are referenced by this instrument.
|
||
|
for(auto sample : pIns->GetSamples())
|
||
|
{
|
||
|
if(sample <= sndFile.GetNumSamples())
|
||
|
{
|
||
|
AppendMenu(hSubMenu, MF_STRING, ID_NOTEMAP_EDITSAMPLE + sample, MPT_CFORMAT("{}: {}")(sample, mpt::ToCString(sndFile.GetCharsetInternal(), sndFile.m_szNames[sample])));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
AppendMenu(hMenu, MF_POPUP, reinterpret_cast<UINT_PTR>(hSubMenu), ih->GetKeyTextFromCommand(kcInsNoteMapEditSample, _T("&Edit Sample")));
|
||
|
AppendMenu(hMenu, MF_SEPARATOR, 0, NULL);
|
||
|
}
|
||
|
AppendMenu(hMenu, MF_STRING, ID_NOTEMAP_COPY_SMP, ih->GetKeyTextFromCommand(kcInsNoteMapCopyCurrentSample, MPT_CFORMAT("Map All Notes to &Sample {}")(pIns->Keyboard[m_nNote])));
|
||
|
|
||
|
if(sndFile.GetType() != MOD_TYPE_XM)
|
||
|
{
|
||
|
if(ModCommand::IsNote(pIns->NoteMap[m_nNote]))
|
||
|
{
|
||
|
AppendMenu(hMenu, MF_STRING, ID_NOTEMAP_COPY_NOTE, ih->GetKeyTextFromCommand(kcInsNoteMapCopyCurrentNote, MPT_CFORMAT("Map All &Notes to {}")(mpt::ToCString(sndFile.GetNoteName(pIns->NoteMap[m_nNote], m_nInstrument)))));
|
||
|
}
|
||
|
AppendMenu(hMenu, MF_STRING, ID_NOTEMAP_TRANS_UP, ih->GetKeyTextFromCommand(kcInsNoteMapTransposeUp, _T("Transpose Map &Up")));
|
||
|
AppendMenu(hMenu, MF_STRING, ID_NOTEMAP_TRANS_DOWN, ih->GetKeyTextFromCommand(kcInsNoteMapTransposeDown, _T("Transpose Map &Down")));
|
||
|
}
|
||
|
AppendMenu(hMenu, MF_STRING, ID_NOTEMAP_RESET, ih->GetKeyTextFromCommand(kcInsNoteMapReset, _T("&Reset Note Mapping")));
|
||
|
AppendMenu(hMenu, MF_STRING | (pIns->CanConvertToDefaultNoteMap().empty() ? MF_GRAYED : 0), ID_NOTEMAP_TRANSPOSE_SAMPLES, ih->GetKeyTextFromCommand(kcInsNoteMapTransposeSamples, _T("&Transpose Samples / Reset Map")));
|
||
|
AppendMenu(hMenu, MF_STRING, ID_NOTEMAP_REMOVE, ih->GetKeyTextFromCommand(kcInsNoteMapRemove, _T("Remo&ve All Samples")));
|
||
|
AppendMenu(hMenu, MF_STRING, ID_INSTRUMENT_DUPLICATE, ih->GetKeyTextFromCommand(kcInstrumentCtrlDuplicate, _T("Duplicate &Instrument")));
|
||
|
SetMenuDefaultItem(hMenu, ID_INSTRUMENT_SAMPLEMAP, FALSE);
|
||
|
ClientToScreen(&pt);
|
||
|
::TrackPopupMenu(hMenu, TPM_LEFTALIGN|TPM_RIGHTBUTTON, pt.x, pt.y, 0, m_hWnd, NULL);
|
||
|
::DestroyMenu(hMenu);
|
||
|
if (hSubMenu) ::DestroyMenu(hSubMenu);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL CNoteMapWnd::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
|
||
|
{
|
||
|
SetCurrentNote(m_nNote - mpt::signum(zDelta));
|
||
|
return CStatic::OnMouseWheel(nFlags, zDelta, pt);
|
||
|
}
|
||
|
|
||
|
|
||
|
void CNoteMapWnd::OnMapCopyNote()
|
||
|
{
|
||
|
ModInstrument *pIns = m_modDoc.GetSoundFile().Instruments[m_nInstrument];
|
||
|
if (pIns)
|
||
|
{
|
||
|
m_undo = true;
|
||
|
bool bModified = false;
|
||
|
auto n = pIns->NoteMap[m_nNote];
|
||
|
for (auto &key : pIns->NoteMap) if (key != n)
|
||
|
{
|
||
|
if(!bModified)
|
||
|
{
|
||
|
PrepareUndo("Map Notes");
|
||
|
}
|
||
|
key = n;
|
||
|
bModified = true;
|
||
|
}
|
||
|
if (bModified)
|
||
|
{
|
||
|
m_pParent.SetModified(InstrumentHint().Info(), false);
|
||
|
Invalidate(FALSE);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CNoteMapWnd::OnMapCopySample()
|
||
|
{
|
||
|
ModInstrument *pIns = m_modDoc.GetSoundFile().Instruments[m_nInstrument];
|
||
|
if (pIns)
|
||
|
{
|
||
|
m_undo = true;
|
||
|
bool bModified = false;
|
||
|
auto n = pIns->Keyboard[m_nNote];
|
||
|
for (auto &sample : pIns->Keyboard) if (sample != n)
|
||
|
{
|
||
|
if(!bModified)
|
||
|
{
|
||
|
PrepareUndo("Map Samples");
|
||
|
}
|
||
|
sample = n;
|
||
|
bModified = true;
|
||
|
}
|
||
|
if (bModified)
|
||
|
{
|
||
|
m_pParent.SetModified(InstrumentHint().Info(), false);
|
||
|
Invalidate(FALSE);
|
||
|
UpdateAccessibleTitle();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CNoteMapWnd::OnMapReset()
|
||
|
{
|
||
|
ModInstrument *pIns = m_modDoc.GetSoundFile().Instruments[m_nInstrument];
|
||
|
if (pIns)
|
||
|
{
|
||
|
m_undo = true;
|
||
|
bool modified = false;
|
||
|
for (size_t i = 0; i < std::size(pIns->NoteMap); i++) if (pIns->NoteMap[i] != i + 1)
|
||
|
{
|
||
|
if(!modified)
|
||
|
{
|
||
|
PrepareUndo("Reset Note Map");
|
||
|
}
|
||
|
pIns->NoteMap[i] = static_cast<ModCommand::NOTE>(i + 1);
|
||
|
modified = true;
|
||
|
}
|
||
|
if(modified)
|
||
|
{
|
||
|
m_pParent.SetModified(InstrumentHint().Info(), false);
|
||
|
Invalidate(FALSE);
|
||
|
UpdateAccessibleTitle();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CNoteMapWnd::OnTransposeSamples()
|
||
|
{
|
||
|
auto &sndFile = m_modDoc.GetSoundFile();
|
||
|
ModInstrument *pIns = sndFile.Instruments[m_nInstrument];
|
||
|
if(!pIns)
|
||
|
return;
|
||
|
const auto samples = pIns->CanConvertToDefaultNoteMap();
|
||
|
if(samples.empty())
|
||
|
return;
|
||
|
|
||
|
PrepareUndo("Transpose Samples");
|
||
|
for(const auto &[smp, transpose] : samples)
|
||
|
{
|
||
|
if(smp > sndFile.GetNumSamples())
|
||
|
continue;
|
||
|
m_modDoc.GetSampleUndo().PrepareUndo(smp, sundo_none, "Transpose");
|
||
|
auto &sample = sndFile.GetSample(smp);
|
||
|
if(sndFile.UseFinetuneAndTranspose())
|
||
|
sample.RelativeTone += transpose;
|
||
|
else
|
||
|
sample.Transpose(transpose / 12.0);
|
||
|
m_modDoc.UpdateAllViews(nullptr, SampleHint(smp).Info(), &m_pParent);
|
||
|
}
|
||
|
pIns->ResetNoteMap();
|
||
|
m_pParent.SetModified(InstrumentHint().Info(), false);
|
||
|
Invalidate(FALSE);
|
||
|
}
|
||
|
|
||
|
|
||
|
void CNoteMapWnd::OnMapRemove()
|
||
|
{
|
||
|
ModInstrument *pIns = m_modDoc.GetSoundFile().Instruments[m_nInstrument];
|
||
|
if (pIns)
|
||
|
{
|
||
|
m_undo = true;
|
||
|
bool modified = false;
|
||
|
for (auto &sample: pIns->Keyboard) if (sample != 0)
|
||
|
{
|
||
|
if(!modified)
|
||
|
{
|
||
|
PrepareUndo("Remove Sample Assocations");
|
||
|
}
|
||
|
sample = 0;
|
||
|
modified = true;
|
||
|
}
|
||
|
if(modified)
|
||
|
{
|
||
|
m_pParent.SetModified(InstrumentHint().Info(), false);
|
||
|
Invalidate(FALSE);
|
||
|
UpdateAccessibleTitle();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CNoteMapWnd::OnMapTransposeUp()
|
||
|
{
|
||
|
MapTranspose(1);
|
||
|
}
|
||
|
|
||
|
|
||
|
void CNoteMapWnd::OnMapTransposeDown()
|
||
|
{
|
||
|
MapTranspose(-1);
|
||
|
}
|
||
|
|
||
|
|
||
|
void CNoteMapWnd::MapTranspose(int nAmount)
|
||
|
{
|
||
|
if(nAmount == 0 || m_modDoc.GetModType() == MOD_TYPE_XM) return;
|
||
|
|
||
|
ModInstrument *pIns = m_modDoc.GetSoundFile().Instruments[m_nInstrument];
|
||
|
if((nAmount == 12 || nAmount == -12))
|
||
|
{
|
||
|
// Special case for instrument-specific tunings
|
||
|
nAmount = m_modDoc.GetInstrumentGroupSize(m_nInstrument) * mpt::signum(nAmount);
|
||
|
}
|
||
|
|
||
|
m_undo = true;
|
||
|
if (pIns)
|
||
|
{
|
||
|
bool modified = false;
|
||
|
for(NOTEINDEXTYPE i = 0; i < NOTE_MAX; i++)
|
||
|
{
|
||
|
int n = pIns->NoteMap[i];
|
||
|
if ((n > NOTE_MIN && nAmount < 0) || (n < NOTE_MAX && nAmount > 0))
|
||
|
{
|
||
|
n = Clamp(n + nAmount, NOTE_MIN, NOTE_MAX);
|
||
|
if(n != pIns->NoteMap[i])
|
||
|
{
|
||
|
if(!modified)
|
||
|
{
|
||
|
PrepareUndo("Transpose Map");
|
||
|
}
|
||
|
pIns->NoteMap[i] = static_cast<uint8>(n);
|
||
|
modified = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if(modified)
|
||
|
{
|
||
|
m_pParent.SetModified(InstrumentHint().Info(), false);
|
||
|
Invalidate(FALSE);
|
||
|
UpdateAccessibleTitle();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CNoteMapWnd::OnEditSample(UINT nID)
|
||
|
{
|
||
|
UINT nSample = nID - ID_NOTEMAP_EDITSAMPLE;
|
||
|
m_pParent.EditSample(nSample);
|
||
|
}
|
||
|
|
||
|
|
||
|
void CNoteMapWnd::OnEditSampleMap()
|
||
|
{
|
||
|
m_undo = true;
|
||
|
m_pParent.PostMessage(WM_COMMAND, ID_INSTRUMENT_SAMPLEMAP);
|
||
|
}
|
||
|
|
||
|
|
||
|
void CNoteMapWnd::OnInstrumentDuplicate()
|
||
|
{
|
||
|
m_undo = true;
|
||
|
m_pParent.PostMessage(WM_COMMAND, ID_INSTRUMENT_DUPLICATE);
|
||
|
}
|
||
|
|
||
|
|
||
|
LRESULT CNoteMapWnd::OnCustomKeyMsg(WPARAM wParam, LPARAM lParam)
|
||
|
{
|
||
|
ModInstrument *pIns = m_modDoc.GetSoundFile().Instruments[m_nInstrument];
|
||
|
|
||
|
// Handle notes
|
||
|
|
||
|
if (wParam >= kcInsNoteMapStartNotes && wParam <= kcInsNoteMapEndNotes)
|
||
|
{
|
||
|
// Special case: number keys override notes if we're in the sample # column.
|
||
|
const auto key = KeyCombination::FromLPARAM(lParam).KeyCode();
|
||
|
if(m_bIns && ((key >= '0' && key <= '9') || (key == ' ')))
|
||
|
HandleChar(key);
|
||
|
else
|
||
|
EnterNote(m_modDoc.GetNoteWithBaseOctave(static_cast<int>(wParam - kcInsNoteMapStartNotes), m_nInstrument));
|
||
|
|
||
|
return wParam;
|
||
|
}
|
||
|
|
||
|
if (wParam >= kcInsNoteMapStartNoteStops && wParam <= kcInsNoteMapEndNoteStops)
|
||
|
{
|
||
|
StopNote();
|
||
|
return wParam;
|
||
|
}
|
||
|
|
||
|
// Other shortcuts
|
||
|
|
||
|
switch(wParam)
|
||
|
{
|
||
|
case kcInsNoteMapTransposeDown: MapTranspose(-1); return wParam;
|
||
|
case kcInsNoteMapTransposeUp: MapTranspose(1); return wParam;
|
||
|
case kcInsNoteMapTransposeOctDown: MapTranspose(-12); return wParam;
|
||
|
case kcInsNoteMapTransposeOctUp: MapTranspose(12); return wParam;
|
||
|
|
||
|
case kcInsNoteMapCopyCurrentSample: OnMapCopySample(); return wParam;
|
||
|
case kcInsNoteMapCopyCurrentNote: OnMapCopyNote(); return wParam;
|
||
|
case kcInsNoteMapReset: OnMapReset(); return wParam;
|
||
|
case kcInsNoteMapTransposeSamples: OnTransposeSamples(); return wParam;
|
||
|
case kcInsNoteMapRemove: OnMapRemove(); return wParam;
|
||
|
|
||
|
case kcInsNoteMapEditSample: if(pIns) OnEditSample(pIns->Keyboard[m_nNote] + ID_NOTEMAP_EDITSAMPLE); return wParam;
|
||
|
case kcInsNoteMapEditSampleMap: OnEditSampleMap(); return wParam;
|
||
|
|
||
|
// Parent shortcuts (also displayed in context menu of this control)
|
||
|
case kcInstrumentCtrlDuplicate: OnInstrumentDuplicate(); return wParam;
|
||
|
case kcNextInstrument: m_pParent.PostMessage(WM_COMMAND, ID_NEXTINSTRUMENT); return wParam;
|
||
|
case kcPrevInstrument: m_pParent.PostMessage(WM_COMMAND, ID_PREVINSTRUMENT); return wParam;
|
||
|
}
|
||
|
|
||
|
return kcNull;
|
||
|
}
|
||
|
|
||
|
void CNoteMapWnd::EnterNote(UINT note)
|
||
|
{
|
||
|
CSoundFile &sndFile = m_modDoc.GetSoundFile();
|
||
|
ModInstrument *pIns = sndFile.Instruments[m_nInstrument];
|
||
|
if ((pIns) && (m_nNote < NOTE_MAX))
|
||
|
{
|
||
|
if (!m_bIns && (sndFile.GetType() & (MOD_TYPE_IT|MOD_TYPE_MPT)))
|
||
|
{
|
||
|
UINT n = pIns->NoteMap[m_nNote];
|
||
|
bool ok = false;
|
||
|
if ((note >= sndFile.GetModSpecifications().noteMin) && (note <= sndFile.GetModSpecifications().noteMax))
|
||
|
{
|
||
|
n = note;
|
||
|
ok = true;
|
||
|
}
|
||
|
if (n != pIns->NoteMap[m_nNote])
|
||
|
{
|
||
|
StopNote(); // Stop old note according to current instrument settings
|
||
|
pIns->NoteMap[m_nNote] = static_cast<ModCommand::NOTE>(n);
|
||
|
m_pParent.SetModified(InstrumentHint().Info(), false);
|
||
|
Invalidate(FALSE);
|
||
|
UpdateAccessibleTitle();
|
||
|
}
|
||
|
if(ok)
|
||
|
{
|
||
|
PlayNote(m_nNote);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool CNoteMapWnd::HandleChar(WPARAM c)
|
||
|
{
|
||
|
CSoundFile &sndFile = m_modDoc.GetSoundFile();
|
||
|
ModInstrument *pIns = sndFile.Instruments[m_nInstrument];
|
||
|
if ((pIns) && (m_nNote < NOTE_MAX))
|
||
|
{
|
||
|
|
||
|
if ((m_bIns) && (((c >= '0') && (c <= '9')) || (c == ' '))) //in sample # column
|
||
|
{
|
||
|
UINT n = m_nOldIns;
|
||
|
if (c != ' ')
|
||
|
{
|
||
|
n = (10 * pIns->Keyboard[m_nNote] + (c - '0')) % 10000;
|
||
|
if ((n >= MAX_SAMPLES) || ((sndFile.m_nSamples < 1000) && (n >= 1000))) n = (n % 1000);
|
||
|
if ((n >= MAX_SAMPLES) || ((sndFile.m_nSamples < 100) && (n >= 100))) n = (n % 100); else
|
||
|
if ((n > 31) && (sndFile.m_nSamples < 32) && (n % 10)) n = (n % 10);
|
||
|
}
|
||
|
|
||
|
if (n != pIns->Keyboard[m_nNote])
|
||
|
{
|
||
|
if(m_undo)
|
||
|
{
|
||
|
PrepareUndo("Enter Instrument");
|
||
|
m_undo = false;
|
||
|
}
|
||
|
StopNote(); // Stop old note according to current instrument settings
|
||
|
pIns->Keyboard[m_nNote] = static_cast<SAMPLEINDEX>(n);
|
||
|
m_pParent.SetModified(InstrumentHint().Info(), false);
|
||
|
Invalidate(FALSE);
|
||
|
UpdateAccessibleTitle();
|
||
|
PlayNote(m_nNote);
|
||
|
}
|
||
|
|
||
|
if (c == ' ')
|
||
|
{
|
||
|
SetCurrentNote(m_nNote + 1);
|
||
|
PlayNote(m_nNote);
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
else if ((!m_bIns) && (sndFile.m_nType & (MOD_TYPE_IT | MOD_TYPE_MPT))) //in note column
|
||
|
{
|
||
|
uint32 n = pIns->NoteMap[m_nNote];
|
||
|
|
||
|
if ((c >= '0') && (c <= '9'))
|
||
|
{
|
||
|
if (n)
|
||
|
n = static_cast<uint32>(((n - 1) % 12) + (c - '0') * 12 + 1);
|
||
|
else
|
||
|
n = static_cast<uint32>((m_nNote % 12) + (c - '0') * 12 + 1);
|
||
|
} else if (c == ' ')
|
||
|
{
|
||
|
n = (m_nOldNote) ? m_nOldNote : m_nNote+1;
|
||
|
}
|
||
|
|
||
|
if (n != pIns->NoteMap[m_nNote])
|
||
|
{
|
||
|
if(m_undo)
|
||
|
{
|
||
|
PrepareUndo("Enter Note");
|
||
|
m_undo = false;
|
||
|
}
|
||
|
|
||
|
StopNote(); // Stop old note according to current instrument settings
|
||
|
pIns->NoteMap[m_nNote] = static_cast<ModCommand::NOTE>(n);
|
||
|
m_pParent.SetModified(InstrumentHint().Info(), false);
|
||
|
Invalidate(FALSE);
|
||
|
UpdateAccessibleTitle();
|
||
|
}
|
||
|
|
||
|
if(c == ' ')
|
||
|
{
|
||
|
SetCurrentNote(m_nNote + 1);
|
||
|
}
|
||
|
|
||
|
PlayNote(m_nNote);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool CNoteMapWnd::HandleNav(WPARAM k)
|
||
|
{
|
||
|
bool redraw = false;
|
||
|
|
||
|
//HACK: handle numpad (convert numpad number key to normal number key)
|
||
|
if ((k >= VK_NUMPAD0) && (k <= VK_NUMPAD9)) return HandleChar(k-VK_NUMPAD0+'0');
|
||
|
|
||
|
switch(k)
|
||
|
{
|
||
|
case VK_RIGHT:
|
||
|
if (!m_bIns) { m_bIns = true; redraw = true; } else
|
||
|
if (m_nNote < NOTE_MAX - NOTE_MIN) { m_nNote++; m_bIns = false; redraw = true; }
|
||
|
break;
|
||
|
case VK_LEFT:
|
||
|
if (m_bIns) { m_bIns = false; redraw = true; } else
|
||
|
if (m_nNote) { m_nNote--; m_bIns = true; redraw = true; }
|
||
|
break;
|
||
|
case VK_UP:
|
||
|
if (m_nNote > 0) { m_nNote--; redraw = true; }
|
||
|
break;
|
||
|
case VK_DOWN:
|
||
|
if (m_nNote < NOTE_MAX - 1) { m_nNote++; redraw = true; }
|
||
|
break;
|
||
|
case VK_PRIOR:
|
||
|
if (m_nNote > 3) { m_nNote -= 3; redraw = true; } else
|
||
|
if (m_nNote > 0) { m_nNote = 0; redraw = true; }
|
||
|
break;
|
||
|
case VK_NEXT:
|
||
|
if (m_nNote+3 < NOTE_MAX) { m_nNote += 3; redraw = true; } else
|
||
|
if (m_nNote < NOTE_MAX - NOTE_MIN) { m_nNote = NOTE_MAX - NOTE_MIN; redraw = true; }
|
||
|
break;
|
||
|
case VK_HOME:
|
||
|
if(m_nNote > 0) { m_nNote = 0; redraw = true; }
|
||
|
break;
|
||
|
case VK_END:
|
||
|
if(m_nNote < NOTE_MAX - NOTE_MIN) { m_nNote = NOTE_MAX - NOTE_MIN; redraw = true; }
|
||
|
break;
|
||
|
// case VK_TAB:
|
||
|
// return true;
|
||
|
case VK_RETURN:
|
||
|
{
|
||
|
ModInstrument *pIns = m_modDoc.GetSoundFile().Instruments[m_nInstrument];
|
||
|
if(pIns)
|
||
|
{
|
||
|
if (m_bIns)
|
||
|
m_nOldIns = pIns->Keyboard[m_nNote];
|
||
|
else
|
||
|
m_nOldNote = pIns->NoteMap[m_nNote];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
default:
|
||
|
return false;
|
||
|
}
|
||
|
if(redraw)
|
||
|
{
|
||
|
m_undo = true;
|
||
|
Invalidate(FALSE);
|
||
|
UpdateAccessibleTitle();
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
void CNoteMapWnd::PlayNote(UINT note)
|
||
|
{
|
||
|
if(m_nPlayingNote != NOTE_NONE)
|
||
|
{
|
||
|
// No polyphony in notemap window
|
||
|
StopNote();
|
||
|
}
|
||
|
m_nPlayingNote = static_cast<ModCommand::NOTE>(note + NOTE_MIN);
|
||
|
m_noteChannel = m_modDoc.PlayNote(PlayNoteParam(m_nPlayingNote).Instrument(m_nInstrument));
|
||
|
}
|
||
|
|
||
|
|
||
|
void CNoteMapWnd::StopNote()
|
||
|
{
|
||
|
if(!ModCommand::IsNote(m_nPlayingNote)) return;
|
||
|
|
||
|
m_modDoc.NoteOff(m_nPlayingNote, true, m_nInstrument, m_noteChannel);
|
||
|
m_nPlayingNote = NOTE_NONE;
|
||
|
}
|
||
|
|
||
|
|
||
|
void CNoteMapWnd::UpdateAccessibleTitle()
|
||
|
{
|
||
|
CMainFrame::GetMainFrame()->NotifyAccessibilityUpdate(*this);
|
||
|
}
|
||
|
|
||
|
|
||
|
// Accessible description for screen readers
|
||
|
HRESULT CNoteMapWnd::get_accName(VARIANT varChild, BSTR *pszName)
|
||
|
{
|
||
|
const auto *ins = m_modDoc.GetSoundFile().Instruments[m_nInstrument];
|
||
|
if(!ins || m_nNote >= std::size(ins->NoteMap))
|
||
|
return CStatic::get_accName(varChild, pszName);
|
||
|
|
||
|
const auto &sndFile = m_modDoc.GetSoundFile();
|
||
|
CString str = mpt::ToCString(sndFile.GetNoteName(static_cast<ModCommand::NOTE>(m_nNote + NOTE_MIN), m_nInstrument)) + _T(": ");
|
||
|
if(ins->Keyboard[m_nNote] == 0)
|
||
|
{
|
||
|
str += _T("no sample");
|
||
|
} else
|
||
|
{
|
||
|
auto mappedNote = ins->NoteMap[m_nNote];
|
||
|
str += MPT_CFORMAT("sample {} at {}")(ins->Keyboard[m_nNote], mpt::ToCString(sndFile.GetNoteName(mappedNote, m_nInstrument)));
|
||
|
}
|
||
|
|
||
|
*pszName = str.AllocSysString();
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////
|
||
|
// CCtrlInstruments
|
||
|
|
||
|
#define MAX_ATTACK_LENGTH 2001
|
||
|
#define MAX_ATTACK_VALUE (MAX_ATTACK_LENGTH - 1) // 16 bit unsigned max
|
||
|
|
||
|
BEGIN_MESSAGE_MAP(CCtrlInstruments, CModControlDlg)
|
||
|
//{{AFX_MSG_MAP(CCtrlInstruments)
|
||
|
ON_WM_VSCROLL()
|
||
|
ON_WM_HSCROLL()
|
||
|
ON_WM_XBUTTONUP()
|
||
|
ON_NOTIFY(TBN_DROPDOWN, IDC_TOOLBAR1, &CCtrlInstruments::OnTbnDropDownToolBar)
|
||
|
ON_COMMAND(IDC_INSTRUMENT_NEW, &CCtrlInstruments::OnInstrumentNew)
|
||
|
ON_COMMAND(IDC_INSTRUMENT_OPEN, &CCtrlInstruments::OnInstrumentOpen)
|
||
|
ON_COMMAND(IDC_INSTRUMENT_SAVEAS, &CCtrlInstruments::OnInstrumentSave)
|
||
|
ON_COMMAND(IDC_SAVE_ONE, &CCtrlInstruments::OnInstrumentSaveOne)
|
||
|
ON_COMMAND(IDC_SAVE_ALL, &CCtrlInstruments::OnInstrumentSaveAll)
|
||
|
ON_COMMAND(IDC_INSTRUMENT_PLAY, &CCtrlInstruments::OnInstrumentPlay)
|
||
|
ON_COMMAND(ID_PREVINSTRUMENT, &CCtrlInstruments::OnPrevInstrument)
|
||
|
ON_COMMAND(ID_NEXTINSTRUMENT, &CCtrlInstruments::OnNextInstrument)
|
||
|
ON_COMMAND(ID_INSTRUMENT_DUPLICATE, &CCtrlInstruments::OnInstrumentDuplicate)
|
||
|
ON_COMMAND(IDC_CHECK1, &CCtrlInstruments::OnSetPanningChanged)
|
||
|
ON_COMMAND(IDC_CHECK2, &CCtrlInstruments::OnEnableCutOff)
|
||
|
ON_COMMAND(IDC_CHECK3, &CCtrlInstruments::OnEnableResonance)
|
||
|
ON_COMMAND(IDC_INSVIEWPLG, &CCtrlInstruments::TogglePluginEditor)
|
||
|
ON_EN_CHANGE(IDC_EDIT_INSTRUMENT, &CCtrlInstruments::OnInstrumentChanged)
|
||
|
ON_EN_CHANGE(IDC_SAMPLE_NAME, &CCtrlInstruments::OnNameChanged)
|
||
|
ON_EN_CHANGE(IDC_SAMPLE_FILENAME, &CCtrlInstruments::OnFileNameChanged)
|
||
|
ON_EN_CHANGE(IDC_EDIT7, &CCtrlInstruments::OnFadeOutVolChanged)
|
||
|
ON_EN_CHANGE(IDC_EDIT8, &CCtrlInstruments::OnGlobalVolChanged)
|
||
|
ON_EN_CHANGE(IDC_EDIT9, &CCtrlInstruments::OnPanningChanged)
|
||
|
ON_EN_CHANGE(IDC_EDIT10, &CCtrlInstruments::OnMPRChanged)
|
||
|
ON_EN_KILLFOCUS(IDC_EDIT10, &CCtrlInstruments::OnMPRKillFocus)
|
||
|
ON_EN_CHANGE(IDC_EDIT11, &CCtrlInstruments::OnMBKChanged)
|
||
|
ON_EN_CHANGE(IDC_EDIT15, &CCtrlInstruments::OnPPSChanged)
|
||
|
ON_EN_CHANGE(IDC_PITCHWHEELDEPTH, &CCtrlInstruments::OnPitchWheelDepthChanged)
|
||
|
ON_EN_CHANGE(IDC_EDIT2, &CCtrlInstruments::OnAttackChanged)
|
||
|
|
||
|
ON_EN_SETFOCUS(IDC_SAMPLE_NAME, &CCtrlInstruments::OnEditFocus)
|
||
|
ON_EN_SETFOCUS(IDC_SAMPLE_FILENAME, &CCtrlInstruments::OnEditFocus)
|
||
|
ON_EN_SETFOCUS(IDC_EDIT7, &CCtrlInstruments::OnEditFocus)
|
||
|
ON_EN_SETFOCUS(IDC_EDIT8, &CCtrlInstruments::OnEditFocus)
|
||
|
ON_EN_SETFOCUS(IDC_EDIT9, &CCtrlInstruments::OnEditFocus)
|
||
|
ON_EN_SETFOCUS(IDC_EDIT10, &CCtrlInstruments::OnEditFocus)
|
||
|
ON_EN_SETFOCUS(IDC_EDIT11, &CCtrlInstruments::OnEditFocus)
|
||
|
ON_EN_SETFOCUS(IDC_EDIT15, &CCtrlInstruments::OnEditFocus)
|
||
|
ON_EN_SETFOCUS(IDC_PITCHWHEELDEPTH, &CCtrlInstruments::OnEditFocus)
|
||
|
ON_EN_SETFOCUS(IDC_EDIT2, &CCtrlInstruments::OnEditFocus)
|
||
|
ON_EN_SETFOCUS(IDC_EDIT_PITCHTEMPOLOCK, &CCtrlInstruments::OnEditFocus)
|
||
|
|
||
|
ON_CBN_SELCHANGE(IDC_COMBO1, &CCtrlInstruments::OnNNAChanged)
|
||
|
ON_CBN_SELCHANGE(IDC_COMBO2, &CCtrlInstruments::OnDCTChanged)
|
||
|
ON_CBN_SELCHANGE(IDC_COMBO3, &CCtrlInstruments::OnDCAChanged)
|
||
|
ON_CBN_SELCHANGE(IDC_COMBO4, &CCtrlInstruments::OnPPCChanged)
|
||
|
ON_CBN_SELCHANGE(IDC_COMBO5, &CCtrlInstruments::OnMCHChanged)
|
||
|
ON_CBN_SELCHANGE(IDC_COMBO6, &CCtrlInstruments::OnMixPlugChanged)
|
||
|
ON_CBN_DROPDOWN(IDC_COMBO6, &CCtrlInstruments::OnOpenPluginList)
|
||
|
ON_CBN_SELCHANGE(IDC_COMBO9, &CCtrlInstruments::OnResamplingChanged)
|
||
|
ON_CBN_SELCHANGE(IDC_FILTERMODE, &CCtrlInstruments::OnFilterModeChanged)
|
||
|
ON_CBN_SELCHANGE(IDC_PLUGIN_VOLUMESTYLE, &CCtrlInstruments::OnPluginVolumeHandlingChanged)
|
||
|
ON_COMMAND(IDC_PLUGIN_VELOCITYSTYLE, &CCtrlInstruments::OnPluginVelocityHandlingChanged)
|
||
|
ON_COMMAND(ID_INSTRUMENT_SAMPLEMAP, &CCtrlInstruments::OnEditSampleMap)
|
||
|
ON_CBN_SELCHANGE(IDC_COMBOTUNING, &CCtrlInstruments::OnCbnSelchangeCombotuning)
|
||
|
ON_EN_CHANGE(IDC_EDIT_PITCHTEMPOLOCK, &CCtrlInstruments::OnEnChangeEditPitchTempoLock)
|
||
|
ON_BN_CLICKED(IDC_CHECK_PITCHTEMPOLOCK, &CCtrlInstruments::OnBnClickedCheckPitchtempolock)
|
||
|
ON_EN_KILLFOCUS(IDC_EDIT_PITCHTEMPOLOCK, &CCtrlInstruments::OnEnKillFocusEditPitchTempoLock)
|
||
|
ON_EN_KILLFOCUS(IDC_EDIT7, &CCtrlInstruments::OnEnKillFocusEditFadeOut)
|
||
|
//}}AFX_MSG_MAP
|
||
|
END_MESSAGE_MAP()
|
||
|
|
||
|
void CCtrlInstruments::DoDataExchange(CDataExchange* pDX)
|
||
|
{
|
||
|
CModControlDlg::DoDataExchange(pDX);
|
||
|
//{{AFX_DATA_MAP(CCtrlInstruments)
|
||
|
DDX_Control(pDX, IDC_TOOLBAR1, m_ToolBar);
|
||
|
DDX_Control(pDX, IDC_NOTEMAP, m_NoteMap);
|
||
|
DDX_Control(pDX, IDC_SAMPLE_NAME, m_EditName);
|
||
|
DDX_Control(pDX, IDC_SAMPLE_FILENAME, m_EditFileName);
|
||
|
DDX_Control(pDX, IDC_SPIN_INSTRUMENT, m_SpinInstrument);
|
||
|
DDX_Control(pDX, IDC_COMBO1, m_ComboNNA);
|
||
|
DDX_Control(pDX, IDC_COMBO2, m_ComboDCT);
|
||
|
DDX_Control(pDX, IDC_COMBO3, m_ComboDCA);
|
||
|
DDX_Control(pDX, IDC_COMBO4, m_ComboPPC);
|
||
|
DDX_Control(pDX, IDC_COMBO5, m_CbnMidiCh);
|
||
|
DDX_Control(pDX, IDC_COMBO6, m_CbnMixPlug);
|
||
|
DDX_Control(pDX, IDC_COMBO9, m_CbnResampling);
|
||
|
DDX_Control(pDX, IDC_FILTERMODE, m_CbnFilterMode);
|
||
|
DDX_Control(pDX, IDC_EDIT7, m_EditFadeOut);
|
||
|
DDX_Control(pDX, IDC_SPIN7, m_SpinFadeOut);
|
||
|
DDX_Control(pDX, IDC_SPIN8, m_SpinGlobalVol);
|
||
|
DDX_Control(pDX, IDC_SPIN9, m_SpinPanning);
|
||
|
DDX_Control(pDX, IDC_SPIN10, m_SpinMidiPR);
|
||
|
DDX_Control(pDX, IDC_SPIN11, m_SpinMidiBK);
|
||
|
DDX_Control(pDX, IDC_SPIN12, m_SpinPPS);
|
||
|
DDX_Control(pDX, IDC_EDIT8, m_EditGlobalVol);
|
||
|
DDX_Control(pDX, IDC_EDIT9, m_EditPanning);
|
||
|
DDX_Control(pDX, IDC_CHECK1, m_CheckPanning);
|
||
|
DDX_Control(pDX, IDC_CHECK2, m_CheckCutOff);
|
||
|
DDX_Control(pDX, IDC_CHECK3, m_CheckResonance);
|
||
|
DDX_Control(pDX, IDC_SLIDER1, m_SliderVolSwing);
|
||
|
DDX_Control(pDX, IDC_SLIDER2, m_SliderPanSwing);
|
||
|
DDX_Control(pDX, IDC_SLIDER3, m_SliderCutOff);
|
||
|
DDX_Control(pDX, IDC_SLIDER4, m_SliderResonance);
|
||
|
DDX_Control(pDX, IDC_SLIDER6, m_SliderCutSwing);
|
||
|
DDX_Control(pDX, IDC_SLIDER7, m_SliderResSwing);
|
||
|
DDX_Control(pDX, IDC_SLIDER5, m_SliderAttack);
|
||
|
DDX_Control(pDX, IDC_SPIN1, m_SpinAttack);
|
||
|
DDX_Control(pDX, IDC_COMBOTUNING, m_ComboTuning);
|
||
|
DDX_Control(pDX, IDC_CHECK_PITCHTEMPOLOCK, m_CheckPitchTempoLock);
|
||
|
DDX_Control(pDX, IDC_PLUGIN_VOLUMESTYLE, m_CbnPluginVolumeHandling);
|
||
|
DDX_Control(pDX, IDC_PLUGIN_VELOCITYSTYLE, velocityStyle);
|
||
|
DDX_Control(pDX, IDC_SPIN2, m_SpinPWD);
|
||
|
//}}AFX_DATA_MAP
|
||
|
}
|
||
|
|
||
|
|
||
|
CCtrlInstruments::CCtrlInstruments(CModControlView &parent, CModDoc &document)
|
||
|
: CModControlDlg(parent, document)
|
||
|
, m_NoteMap(*this, document)
|
||
|
{
|
||
|
m_nLockCount = 1;
|
||
|
}
|
||
|
|
||
|
|
||
|
CRuntimeClass *CCtrlInstruments::GetAssociatedViewClass()
|
||
|
{
|
||
|
return RUNTIME_CLASS(CViewInstrument);
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnEditFocus()
|
||
|
{
|
||
|
m_startedEdit = false;
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL CCtrlInstruments::OnInitDialog()
|
||
|
{
|
||
|
CModControlDlg::OnInitDialog();
|
||
|
m_bInitialized = FALSE;
|
||
|
SetRedraw(FALSE);
|
||
|
|
||
|
m_ToolBar.SetExtendedStyle(m_ToolBar.GetExtendedStyle() | TBSTYLE_EX_DRAWDDARROWS);
|
||
|
m_ToolBar.Init(CMainFrame::GetMainFrame()->m_PatternIcons,CMainFrame::GetMainFrame()->m_PatternIconsDisabled);
|
||
|
m_ToolBar.AddButton(IDC_INSTRUMENT_NEW, TIMAGE_INSTR_NEW, TBSTYLE_BUTTON | TBSTYLE_DROPDOWN);
|
||
|
m_ToolBar.AddButton(IDC_INSTRUMENT_OPEN, TIMAGE_OPEN);
|
||
|
m_ToolBar.AddButton(IDC_INSTRUMENT_SAVEAS, TIMAGE_SAVE, TBSTYLE_BUTTON | TBSTYLE_DROPDOWN);
|
||
|
m_ToolBar.AddButton(IDC_INSTRUMENT_PLAY, TIMAGE_PREVIEW);
|
||
|
m_SpinInstrument.SetRange(0, 0);
|
||
|
m_SpinInstrument.EnableWindow(FALSE);
|
||
|
// NNA
|
||
|
m_ComboNNA.AddString(_T("Note Cut"));
|
||
|
m_ComboNNA.AddString(_T("Continue"));
|
||
|
m_ComboNNA.AddString(_T("Note Off"));
|
||
|
m_ComboNNA.AddString(_T("Note Fade"));
|
||
|
// DCT
|
||
|
m_ComboDCT.AddString(_T("Disabled"));
|
||
|
m_ComboDCT.AddString(_T("Note"));
|
||
|
m_ComboDCT.AddString(_T("Sample"));
|
||
|
m_ComboDCT.AddString(_T("Instrument"));
|
||
|
m_ComboDCT.AddString(_T("Plugin"));
|
||
|
// DCA
|
||
|
m_ComboDCA.AddString(_T("Note Cut"));
|
||
|
m_ComboDCA.AddString(_T("Note Off"));
|
||
|
m_ComboDCA.AddString(_T("Note Fade"));
|
||
|
// FadeOut Volume
|
||
|
m_SpinFadeOut.SetRange(0, 8192);
|
||
|
// Global Volume
|
||
|
m_SpinGlobalVol.SetRange(0, 64);
|
||
|
// Panning
|
||
|
m_SpinPanning.SetRange(0, (m_modDoc.GetModType() & MOD_TYPE_IT) ? 64 : 256);
|
||
|
// Midi Program
|
||
|
m_SpinMidiPR.SetRange(0, 128);
|
||
|
// Midi Bank
|
||
|
m_SpinMidiBK.SetRange(0, 16384);
|
||
|
// MIDI Pitch Wheel Depth
|
||
|
m_EditPWD.SubclassDlgItem(IDC_PITCHWHEELDEPTH, this);
|
||
|
m_EditPWD.AllowFractions(false);
|
||
|
|
||
|
const auto resamplingModes = Resampling::AllModes();
|
||
|
m_CbnResampling.SetItemData(m_CbnResampling.AddString(_T("Default")), SRCMODE_DEFAULT);
|
||
|
for(auto mode : resamplingModes)
|
||
|
{
|
||
|
m_CbnResampling.SetItemData(m_CbnResampling.AddString(CTrackApp::GetResamplingModeName(mode, 1, false)), mode);
|
||
|
}
|
||
|
|
||
|
m_CbnFilterMode.SetItemData(m_CbnFilterMode.AddString(_T("Channel default")), static_cast<DWORD_PTR>(FilterMode::Unchanged));
|
||
|
m_CbnFilterMode.SetItemData(m_CbnFilterMode.AddString(_T("Force lowpass")), static_cast<DWORD_PTR>(FilterMode::LowPass));
|
||
|
m_CbnFilterMode.SetItemData(m_CbnFilterMode.AddString(_T("Force highpass")), static_cast<DWORD_PTR>(FilterMode::HighPass));
|
||
|
|
||
|
//VST velocity/volume handling
|
||
|
m_CbnPluginVolumeHandling.AddString(_T("MIDI volume"));
|
||
|
m_CbnPluginVolumeHandling.AddString(_T("Dry/Wet ratio"));
|
||
|
m_CbnPluginVolumeHandling.AddString(_T("None"));
|
||
|
|
||
|
// Vol/Pan Swing
|
||
|
m_SliderVolSwing.SetRange(0, 100);
|
||
|
m_SliderPanSwing.SetRange(0, 64);
|
||
|
m_SliderCutSwing.SetRange(0, 64);
|
||
|
m_SliderResSwing.SetRange(0, 64);
|
||
|
// Filter
|
||
|
m_SliderCutOff.SetRange(0x00, 0x7F);
|
||
|
m_SliderResonance.SetRange(0x00, 0x7F);
|
||
|
// Pitch/Pan Separation
|
||
|
m_EditPPS.SubclassDlgItem(IDC_EDIT15, this);
|
||
|
m_EditPPS.AllowFractions(false);
|
||
|
m_SpinPPS.SetRange(-32, +32);
|
||
|
// Pitch/Pan Center
|
||
|
SetWindowLongPtr(m_ComboPPC.m_hWnd, GWLP_USERDATA, 0);
|
||
|
|
||
|
// Volume ramping (attack)
|
||
|
m_SliderAttack.SetRange(0,MAX_ATTACK_VALUE);
|
||
|
m_SpinAttack.SetRange(0,MAX_ATTACK_VALUE);
|
||
|
|
||
|
m_SpinInstrument.SetFocus();
|
||
|
|
||
|
m_EditPWD.EnableWindow(FALSE);
|
||
|
|
||
|
BuildTuningComboBox();
|
||
|
|
||
|
CheckDlgButton(IDC_CHECK_PITCHTEMPOLOCK, BST_UNCHECKED);
|
||
|
m_EditPitchTempoLock.SubclassDlgItem(IDC_EDIT_PITCHTEMPOLOCK, this);
|
||
|
m_EditPitchTempoLock.AllowNegative(false);
|
||
|
m_EditPitchTempoLock.SetLimitText(9);
|
||
|
|
||
|
SetRedraw(TRUE);
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::RecalcLayout()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnTbnDropDownToolBar(NMHDR *pNMHDR, LRESULT *pResult)
|
||
|
{
|
||
|
CInputHandler *ih = CMainFrame::GetInputHandler();
|
||
|
NMTOOLBAR *pToolBar = reinterpret_cast<NMTOOLBAR *>(pNMHDR);
|
||
|
ClientToScreen(&(pToolBar->rcButton)); // TrackPopupMenu uses screen coords
|
||
|
const int offset = Util::ScalePixels(4, m_hWnd); // Compared to the main toolbar, the offset seems to be a bit wrong here...?
|
||
|
int x = pToolBar->rcButton.left + offset, y = pToolBar->rcButton.bottom + offset;
|
||
|
CMenu menu;
|
||
|
switch(pToolBar->iItem)
|
||
|
{
|
||
|
case IDC_INSTRUMENT_NEW:
|
||
|
{
|
||
|
menu.CreatePopupMenu();
|
||
|
menu.AppendMenu(MF_STRING, ID_INSTRUMENT_DUPLICATE, ih->GetKeyTextFromCommand(kcInstrumentCtrlDuplicate, _T("Duplicate &Instrument")));
|
||
|
menu.TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, x, y, this);
|
||
|
menu.DestroyMenu();
|
||
|
}
|
||
|
break;
|
||
|
case IDC_INSTRUMENT_SAVEAS:
|
||
|
{
|
||
|
menu.CreatePopupMenu();
|
||
|
menu.AppendMenu(MF_STRING, IDC_SAVE_ALL, _T("Save &All..."));
|
||
|
menu.TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, x, y, this);
|
||
|
menu.DestroyMenu();
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
*pResult = 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::PrepareUndo(const char *description)
|
||
|
{
|
||
|
m_startedEdit = true;
|
||
|
m_modDoc.GetInstrumentUndo().PrepareUndo(m_nInstrument, description);
|
||
|
}
|
||
|
|
||
|
|
||
|
// Set document as modified and update other views.
|
||
|
// updateAll: Update all views including this one. Otherwise, only update update other views.
|
||
|
void CCtrlInstruments::SetModified(InstrumentHint hint, bool updateAll)
|
||
|
{
|
||
|
m_modDoc.SetModified();
|
||
|
m_modDoc.UpdateAllViews(nullptr, hint.SetData(m_nInstrument), updateAll ? nullptr : this);
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL CCtrlInstruments::SetCurrentInstrument(UINT nIns, BOOL bUpdNum)
|
||
|
{
|
||
|
if (m_sndFile.m_nInstruments < 1) return FALSE;
|
||
|
if ((nIns < 1) || (nIns > m_sndFile.m_nInstruments)) return FALSE;
|
||
|
LockControls();
|
||
|
if ((m_nInstrument != nIns) || (!m_bInitialized))
|
||
|
{
|
||
|
m_nInstrument = static_cast<INSTRUMENTINDEX>(nIns);
|
||
|
m_NoteMap.SetCurrentInstrument(m_nInstrument);
|
||
|
UpdateView(InstrumentHint(m_nInstrument).Info().Envelope(), NULL);
|
||
|
} else
|
||
|
{
|
||
|
// Just in case
|
||
|
m_NoteMap.SetCurrentInstrument(m_nInstrument);
|
||
|
}
|
||
|
if (bUpdNum)
|
||
|
{
|
||
|
SetDlgItemInt(IDC_EDIT_INSTRUMENT, m_nInstrument);
|
||
|
m_SpinInstrument.SetRange(1, m_sndFile.GetNumInstruments());
|
||
|
m_SpinInstrument.EnableWindow((m_sndFile.GetNumInstruments()) ? TRUE : FALSE);
|
||
|
// Is this a bug ?
|
||
|
m_SliderCutOff.Invalidate(FALSE);
|
||
|
m_SliderResonance.Invalidate(FALSE);
|
||
|
// Volume ramping (attack)
|
||
|
m_SliderAttack.Invalidate(FALSE);
|
||
|
}
|
||
|
SendViewMessage(VIEWMSG_SETCURRENTINSTRUMENT, m_nInstrument);
|
||
|
UnlockControls();
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnActivatePage(LPARAM lParam)
|
||
|
{
|
||
|
CModControlDlg::OnActivatePage(lParam);
|
||
|
if (lParam < 0)
|
||
|
{
|
||
|
int nIns = m_parent.GetInstrumentChange();
|
||
|
if (nIns > 0) lParam = nIns;
|
||
|
} else if(lParam > 0)
|
||
|
{
|
||
|
m_parent.InstrumentChanged(static_cast<INSTRUMENTINDEX>(lParam));
|
||
|
}
|
||
|
|
||
|
UpdatePluginList();
|
||
|
|
||
|
CChildFrame *pFrame = (CChildFrame *)GetParentFrame();
|
||
|
INSTRUMENTVIEWSTATE &instrumentState = pFrame->GetInstrumentViewState();
|
||
|
if(instrumentState.initialInstrument != 0)
|
||
|
{
|
||
|
m_nInstrument = instrumentState.initialInstrument;
|
||
|
instrumentState.initialInstrument = 0;
|
||
|
}
|
||
|
|
||
|
SetCurrentInstrument(static_cast<INSTRUMENTINDEX>((lParam > 0) ? lParam : m_nInstrument));
|
||
|
|
||
|
// Initial Update
|
||
|
if (!m_bInitialized) UpdateView(InstrumentHint(m_nInstrument).Info().Envelope().ModType(), NULL);
|
||
|
|
||
|
PostViewMessage(VIEWMSG_LOADSTATE, (LPARAM)&instrumentState);
|
||
|
SwitchToView();
|
||
|
|
||
|
// Combo boxes randomly disappear without this... why?
|
||
|
Invalidate();
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnDeactivatePage()
|
||
|
{
|
||
|
m_modDoc.NoteOff(0, true);
|
||
|
CChildFrame *pFrame = (CChildFrame *)GetParentFrame();
|
||
|
if ((pFrame) && (m_hWndView)) SendViewMessage(VIEWMSG_SAVESTATE, (LPARAM)&pFrame->GetInstrumentViewState());
|
||
|
CModControlDlg::OnDeactivatePage();
|
||
|
}
|
||
|
|
||
|
|
||
|
LRESULT CCtrlInstruments::OnModCtrlMsg(WPARAM wParam, LPARAM lParam)
|
||
|
{
|
||
|
switch(wParam)
|
||
|
{
|
||
|
case CTRLMSG_GETCURRENTINSTRUMENT:
|
||
|
return m_nInstrument;
|
||
|
break;
|
||
|
|
||
|
case CTRLMSG_INS_PREVINSTRUMENT:
|
||
|
OnPrevInstrument();
|
||
|
break;
|
||
|
|
||
|
case CTRLMSG_INS_NEXTINSTRUMENT:
|
||
|
OnNextInstrument();
|
||
|
break;
|
||
|
|
||
|
case CTRLMSG_INS_OPENFILE:
|
||
|
if(lParam)
|
||
|
return OpenInstrument(*reinterpret_cast<const mpt::PathString *>(lParam));
|
||
|
break;
|
||
|
|
||
|
case CTRLMSG_INS_SONGDROP:
|
||
|
if(lParam)
|
||
|
{
|
||
|
const auto &dropInfo = *reinterpret_cast<const DRAGONDROP *>(lParam);
|
||
|
if(dropInfo.sndFile)
|
||
|
return OpenInstrument(*dropInfo.sndFile, static_cast<INSTRUMENTINDEX>(dropInfo.dropItem));
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case CTRLMSG_INS_NEWINSTRUMENT:
|
||
|
return InsertInstrument(false) ? 1 : 0;
|
||
|
|
||
|
case CTRLMSG_SETCURRENTINSTRUMENT:
|
||
|
SetCurrentInstrument(static_cast<INSTRUMENTINDEX>(lParam));
|
||
|
break;
|
||
|
|
||
|
case CTRLMSG_INS_SAMPLEMAP:
|
||
|
OnEditSampleMap();
|
||
|
break;
|
||
|
|
||
|
case IDC_INSTRUMENT_NEW:
|
||
|
OnInstrumentNew();
|
||
|
break;
|
||
|
case IDC_INSTRUMENT_OPEN:
|
||
|
OnInstrumentOpen();
|
||
|
break;
|
||
|
case IDC_INSTRUMENT_SAVEAS:
|
||
|
OnInstrumentSave();
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
return CModControlDlg::OnModCtrlMsg(wParam, lParam);
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::UpdateView(UpdateHint hint, CObject *pObj)
|
||
|
{
|
||
|
if(pObj == this)
|
||
|
return;
|
||
|
if (hint.GetType()[HINT_MPTOPTIONS])
|
||
|
{
|
||
|
m_ToolBar.UpdateStyle();
|
||
|
hint.ModType(); // For possibly updating note names in Pitch/Pan Separation dropdown
|
||
|
}
|
||
|
LockControls();
|
||
|
if(hint.ToType<PluginHint>().GetType()[HINT_MIXPLUGINS | HINT_PLUGINNAMES])
|
||
|
{
|
||
|
OnMixPlugChanged();
|
||
|
}
|
||
|
if(hint.ToType<GeneralHint>().GetType()[HINT_TUNINGS])
|
||
|
{
|
||
|
BuildTuningComboBox();
|
||
|
}
|
||
|
UnlockControls();
|
||
|
|
||
|
const InstrumentHint instrHint = hint.ToType<InstrumentHint>();
|
||
|
FlagSet<HintType> hintType = instrHint.GetType();
|
||
|
if(!m_bInitialized)
|
||
|
hintType.set(HINT_MODTYPE);
|
||
|
if(!hintType[HINT_MODTYPE | HINT_INSTRUMENT | HINT_ENVELOPE | HINT_INSNAMES])
|
||
|
return;
|
||
|
|
||
|
const INSTRUMENTINDEX updateIns = instrHint.GetInstrument();
|
||
|
if(updateIns != m_nInstrument && updateIns != 0 && !hintType[HINT_MODTYPE])
|
||
|
return;
|
||
|
|
||
|
LockControls();
|
||
|
const ModInstrument *pIns = m_sndFile.Instruments[m_nInstrument];
|
||
|
|
||
|
if(hintType[HINT_MODTYPE])
|
||
|
{
|
||
|
auto &specs = m_sndFile.GetModSpecifications();
|
||
|
|
||
|
// Limit text fields
|
||
|
m_EditName.SetLimitText(specs.instrNameLengthMax);
|
||
|
m_EditFileName.SetLimitText(specs.instrFilenameLengthMax);
|
||
|
|
||
|
const BOOL bITandMPT = ((m_sndFile.GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) && (m_sndFile.GetNumInstruments())) ? TRUE : FALSE;
|
||
|
const BOOL bITandXM = ((m_sndFile.GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_XM)) && (m_sndFile.GetNumInstruments())) ? TRUE : FALSE;
|
||
|
const BOOL bMPTOnly = ((m_sndFile.GetType() == MOD_TYPE_MPT) && (m_sndFile.GetNumInstruments())) ? TRUE : FALSE;
|
||
|
::EnableWindow(::GetDlgItem(m_hWnd, IDC_EDIT10), bITandXM);
|
||
|
::EnableWindow(::GetDlgItem(m_hWnd, IDC_EDIT11), bITandXM);
|
||
|
::EnableWindow(::GetDlgItem(m_hWnd, IDC_EDIT7), bITandXM);
|
||
|
m_EditName.EnableWindow(bITandXM);
|
||
|
m_EditFileName.EnableWindow(bITandMPT);
|
||
|
m_CbnMidiCh.EnableWindow(bITandXM);
|
||
|
m_CbnMixPlug.EnableWindow(bITandXM);
|
||
|
m_SpinMidiPR.EnableWindow(bITandXM);
|
||
|
m_SpinMidiBK.EnableWindow(bITandXM);
|
||
|
|
||
|
const bool extendedFadeoutRange = !(m_sndFile.GetType() & MOD_TYPE_IT);
|
||
|
m_SpinFadeOut.EnableWindow(bITandXM);
|
||
|
m_SpinFadeOut.SetRange(0, extendedFadeoutRange ? 32767 : 8192);
|
||
|
m_EditFadeOut.SetLimitText(extendedFadeoutRange ? 5 : 4);
|
||
|
// XM-style fade-out is 32 times more precise than IT
|
||
|
UDACCEL accell[2];
|
||
|
accell[0].nSec = 0;
|
||
|
accell[0].nInc = (m_sndFile.GetType() == MOD_TYPE_IT ? 32 : 1);
|
||
|
accell[1].nSec = 2;
|
||
|
accell[1].nInc = 5 * accell[0].nInc;
|
||
|
m_SpinFadeOut.SetAccel(mpt::saturate_cast<int>(std::size(accell)), accell);
|
||
|
|
||
|
// Panning ranges (0...64 for IT, 0...256 for MPTM)
|
||
|
m_SpinPanning.SetRange(0, (m_sndFile.GetType() & MOD_TYPE_IT) ? 64 : 256);
|
||
|
|
||
|
// Pitch Wheel Depth
|
||
|
if(m_sndFile.GetType() == MOD_TYPE_XM)
|
||
|
m_SpinPWD.SetRange(0, 36);
|
||
|
else
|
||
|
m_SpinPWD.SetRange(-128, 127);
|
||
|
m_EditPWD.EnableWindow(bITandXM);
|
||
|
m_SpinPWD.EnableWindow(bITandXM);
|
||
|
|
||
|
m_NoteMap.EnableWindow(bITandXM);
|
||
|
|
||
|
m_ComboNNA.EnableWindow(bITandMPT);
|
||
|
m_SliderVolSwing.EnableWindow(bITandMPT);
|
||
|
m_SliderPanSwing.EnableWindow(bITandMPT);
|
||
|
m_ComboDCT.EnableWindow(bITandMPT);
|
||
|
m_ComboDCA.EnableWindow(bITandMPT);
|
||
|
m_ComboPPC.EnableWindow(bITandMPT);
|
||
|
m_SpinPPS.EnableWindow(bITandMPT);
|
||
|
m_EditGlobalVol.EnableWindow(bITandMPT);
|
||
|
m_SpinGlobalVol.EnableWindow(bITandMPT);
|
||
|
m_EditPanning.EnableWindow(bITandMPT);
|
||
|
m_SpinPanning.EnableWindow(bITandMPT);
|
||
|
m_CheckPanning.EnableWindow(bITandMPT);
|
||
|
m_EditPPS.EnableWindow(bITandMPT);
|
||
|
m_CheckCutOff.EnableWindow(bITandMPT);
|
||
|
m_CheckResonance.EnableWindow(bITandMPT);
|
||
|
m_SliderCutOff.EnableWindow(bITandMPT);
|
||
|
m_SliderResonance.EnableWindow(bITandMPT);
|
||
|
m_ComboTuning.EnableWindow(bMPTOnly);
|
||
|
m_EditPitchTempoLock.EnableWindow(bMPTOnly);
|
||
|
m_CheckPitchTempoLock.EnableWindow(bMPTOnly);
|
||
|
|
||
|
// MIDI Channel
|
||
|
// XM has no "mapped" MIDI channels.
|
||
|
m_CbnMidiCh.ResetContent();
|
||
|
for(int ich = MidiNoChannel; ich <= (bITandMPT ? MidiMappedChannel : MidiLastChannel); ich++)
|
||
|
{
|
||
|
CString s;
|
||
|
if (ich == MidiNoChannel)
|
||
|
s = _T("None");
|
||
|
else if (ich == MidiMappedChannel)
|
||
|
s = _T("Mapped");
|
||
|
else
|
||
|
s.Format(_T("%i"), ich);
|
||
|
m_CbnMidiCh.SetItemData(m_CbnMidiCh.AddString(s), ich);
|
||
|
}
|
||
|
}
|
||
|
if(hintType[HINT_MODTYPE | HINT_INSTRUMENT | HINT_INSNAMES])
|
||
|
{
|
||
|
if(pIns)
|
||
|
m_EditName.SetWindowText(mpt::ToCString(m_sndFile.GetCharsetInternal(), pIns->name));
|
||
|
else
|
||
|
m_EditName.SetWindowText(_T(""));
|
||
|
}
|
||
|
if(hintType[HINT_MODTYPE | HINT_INSTRUMENT])
|
||
|
{
|
||
|
m_SpinInstrument.SetRange(1, m_sndFile.m_nInstruments);
|
||
|
m_SpinInstrument.EnableWindow((m_sndFile.m_nInstruments) ? TRUE : FALSE);
|
||
|
|
||
|
// Backwards compatibility with legacy IT/XM modules that use now deprecated hack features.
|
||
|
m_SliderCutSwing.EnableWindow(pIns != nullptr && (m_sndFile.GetType() == MOD_TYPE_MPT || pIns->nCutSwing != 0));
|
||
|
m_SliderResSwing.EnableWindow(pIns != nullptr && (m_sndFile.GetType() == MOD_TYPE_MPT || pIns->nResSwing != 0));
|
||
|
m_CbnFilterMode.EnableWindow (pIns != nullptr && (m_sndFile.GetType() == MOD_TYPE_MPT || pIns->filterMode != FilterMode::Unchanged));
|
||
|
m_CbnResampling.EnableWindow (pIns != nullptr && (m_sndFile.GetType() == MOD_TYPE_MPT || pIns->resampling != SRCMODE_DEFAULT));
|
||
|
m_SliderAttack.EnableWindow (pIns != nullptr && (m_sndFile.GetType() == MOD_TYPE_MPT || pIns->nVolRampUp));
|
||
|
::EnableWindow(::GetDlgItem(m_hWnd, IDC_EDIT2), pIns != nullptr && (m_sndFile.GetType() == MOD_TYPE_MPT || pIns->nVolRampUp));
|
||
|
|
||
|
if (pIns)
|
||
|
{
|
||
|
m_EditFileName.SetWindowText(mpt::ToCString(m_sndFile.GetCharsetInternal(), pIns->filename));
|
||
|
// Fade Out Volume
|
||
|
SetDlgItemInt(IDC_EDIT7, pIns->nFadeOut);
|
||
|
// Global Volume
|
||
|
SetDlgItemInt(IDC_EDIT8, pIns->nGlobalVol);
|
||
|
// Panning
|
||
|
SetDlgItemInt(IDC_EDIT9, (m_modDoc.GetModType() & MOD_TYPE_IT) ? (pIns->nPan / 4) : pIns->nPan);
|
||
|
m_CheckPanning.SetCheck(pIns->dwFlags[INS_SETPANNING] ? TRUE : FALSE);
|
||
|
// Midi
|
||
|
if (pIns->nMidiProgram>0 && pIns->nMidiProgram<=128)
|
||
|
SetDlgItemInt(IDC_EDIT10, pIns->nMidiProgram);
|
||
|
else
|
||
|
SetDlgItemText(IDC_EDIT10, _T("---"));
|
||
|
if (pIns->wMidiBank && pIns->wMidiBank <= 16384)
|
||
|
SetDlgItemInt(IDC_EDIT11, pIns->wMidiBank);
|
||
|
else
|
||
|
SetDlgItemText(IDC_EDIT11, _T("---"));
|
||
|
|
||
|
if (pIns->nMidiChannel < 18)
|
||
|
{
|
||
|
m_CbnMidiCh.SetCurSel(pIns->nMidiChannel);
|
||
|
} else
|
||
|
{
|
||
|
m_CbnMidiCh.SetCurSel(0);
|
||
|
}
|
||
|
if (pIns->nMixPlug <= MAX_MIXPLUGINS)
|
||
|
{
|
||
|
m_CbnMixPlug.SetCurSel(pIns->nMixPlug);
|
||
|
} else
|
||
|
{
|
||
|
m_CbnMixPlug.SetCurSel(0);
|
||
|
}
|
||
|
OnMixPlugChanged();
|
||
|
for(int resMode = 0; resMode<m_CbnResampling.GetCount(); resMode++)
|
||
|
{
|
||
|
if(pIns->resampling == m_CbnResampling.GetItemData(resMode))
|
||
|
{
|
||
|
m_CbnResampling.SetCurSel(resMode);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
for(int fltMode = 0; fltMode<m_CbnFilterMode.GetCount(); fltMode++)
|
||
|
{
|
||
|
if(pIns->filterMode == static_cast<FilterMode>(m_CbnFilterMode.GetItemData(fltMode)))
|
||
|
{
|
||
|
m_CbnFilterMode.SetCurSel(fltMode);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// NNA, DCT, DCA
|
||
|
m_ComboNNA.SetCurSel(static_cast<int>(pIns->nNNA));
|
||
|
m_ComboDCT.SetCurSel(static_cast<int>(pIns->nDCT));
|
||
|
m_ComboDCA.SetCurSel(static_cast<int>(pIns->nDNA));
|
||
|
// Pitch/Pan Separation
|
||
|
if(hintType[HINT_MODTYPE] || pIns->pTuning != (CTuning *)GetWindowLongPtr(m_ComboPPC.m_hWnd, GWLP_USERDATA))
|
||
|
{
|
||
|
// Tuning may have changed, and thus the note names need to be updated
|
||
|
m_ComboPPC.SetRedraw(FALSE);
|
||
|
m_ComboPPC.ResetContent();
|
||
|
AppendNotesToControlEx(m_ComboPPC, m_sndFile, m_nInstrument, NOTE_MIN, NOTE_MAX);
|
||
|
SetWindowLongPtr(m_ComboPPC.m_hWnd, GWLP_USERDATA, (LONG_PTR)pIns->pTuning);
|
||
|
m_ComboPPC.SetRedraw(TRUE);
|
||
|
}
|
||
|
m_ComboPPC.SetCurSel(pIns->nPPC);
|
||
|
ASSERT((uint8)m_ComboPPC.GetItemData(m_ComboPPC.GetCurSel()) == pIns->nPPC + NOTE_MIN);
|
||
|
SetDlgItemInt(IDC_EDIT15, pIns->nPPS);
|
||
|
// Filter
|
||
|
if (m_sndFile.GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT))
|
||
|
{
|
||
|
m_CheckCutOff.SetCheck((pIns->IsCutoffEnabled()) ? TRUE : FALSE);
|
||
|
m_CheckResonance.SetCheck((pIns->IsResonanceEnabled()) ? TRUE : FALSE);
|
||
|
m_SliderVolSwing.SetPos(pIns->nVolSwing);
|
||
|
m_SliderPanSwing.SetPos(pIns->nPanSwing);
|
||
|
m_SliderResSwing.SetPos(pIns->nResSwing);
|
||
|
m_SliderCutSwing.SetPos(pIns->nCutSwing);
|
||
|
m_SliderCutOff.SetPos(pIns->GetCutoff());
|
||
|
m_SliderResonance.SetPos(pIns->GetResonance());
|
||
|
UpdateFilterText();
|
||
|
}
|
||
|
// Volume ramping (attack)
|
||
|
int n = pIns->nVolRampUp; //? MAX_ATTACK_LENGTH - pIns->nVolRampUp : 0;
|
||
|
m_SliderAttack.SetPos(n);
|
||
|
if(n == 0) SetDlgItemText(IDC_EDIT2, _T("default"));
|
||
|
else SetDlgItemInt(IDC_EDIT2,n);
|
||
|
|
||
|
UpdateTuningComboBox();
|
||
|
|
||
|
// Only enable Pitch/Tempo Lock for MPTM files or legacy files that have this property enabled.
|
||
|
m_CheckPitchTempoLock.EnableWindow((m_sndFile.GetType() == MOD_TYPE_MPT || pIns->pitchToTempoLock.GetRaw() > 0) ? TRUE : FALSE);
|
||
|
CheckDlgButton(IDC_CHECK_PITCHTEMPOLOCK, pIns->pitchToTempoLock.GetRaw() > 0 ? BST_CHECKED : BST_UNCHECKED);
|
||
|
m_EditPitchTempoLock.EnableWindow(pIns->pitchToTempoLock.GetRaw() > 0 ? TRUE : FALSE);
|
||
|
if(pIns->pitchToTempoLock.GetRaw() > 0)
|
||
|
{
|
||
|
m_EditPitchTempoLock.SetTempoValue(pIns->pitchToTempoLock);
|
||
|
}
|
||
|
|
||
|
// Pitch Wheel Depth
|
||
|
SetDlgItemInt(IDC_PITCHWHEELDEPTH, pIns->midiPWD, TRUE);
|
||
|
|
||
|
if(m_sndFile.GetType() & (MOD_TYPE_XM|MOD_TYPE_IT|MOD_TYPE_MPT))
|
||
|
{
|
||
|
BOOL enableVol = (m_CbnMixPlug.GetCurSel() > 0 && !m_sndFile.m_playBehaviour[kMIDICCBugEmulation]) ? TRUE : FALSE;
|
||
|
velocityStyle.EnableWindow(enableVol);
|
||
|
m_CbnPluginVolumeHandling.EnableWindow(enableVol);
|
||
|
}
|
||
|
} else
|
||
|
{
|
||
|
m_EditFileName.SetWindowText(_T(""));
|
||
|
velocityStyle.EnableWindow(FALSE);
|
||
|
m_CbnPluginVolumeHandling.EnableWindow(FALSE);
|
||
|
if(m_nInstrument > m_sndFile.GetNumInstruments())
|
||
|
SetCurrentInstrument(m_sndFile.GetNumInstruments());
|
||
|
|
||
|
}
|
||
|
m_NoteMap.Invalidate(FALSE);
|
||
|
|
||
|
m_ComboNNA.Invalidate(FALSE);
|
||
|
m_ComboDCT.Invalidate(FALSE);
|
||
|
m_ComboDCA.Invalidate(FALSE);
|
||
|
m_ComboPPC.Invalidate(FALSE);
|
||
|
m_CbnMidiCh.Invalidate(FALSE);
|
||
|
m_CbnMixPlug.Invalidate(FALSE);
|
||
|
m_CbnResampling.Invalidate(FALSE);
|
||
|
m_CbnFilterMode.Invalidate(FALSE);
|
||
|
m_CbnPluginVolumeHandling.Invalidate(FALSE);
|
||
|
m_ComboTuning.Invalidate(FALSE);
|
||
|
}
|
||
|
if(hint.ToType<PluginHint>().GetType()[HINT_MIXPLUGINS | HINT_PLUGINNAMES | HINT_MODTYPE])
|
||
|
{
|
||
|
UpdatePluginList();
|
||
|
}
|
||
|
|
||
|
if (!m_bInitialized)
|
||
|
{
|
||
|
// First update
|
||
|
m_bInitialized = TRUE;
|
||
|
UnlockControls();
|
||
|
}
|
||
|
|
||
|
UnlockControls();
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::UpdateFilterText()
|
||
|
{
|
||
|
if(m_nInstrument)
|
||
|
{
|
||
|
ModInstrument *pIns = m_sndFile.Instruments[m_nInstrument];
|
||
|
if(pIns)
|
||
|
{
|
||
|
TCHAR s[32];
|
||
|
// In IT Compatible mode, it is enough to just have resonance enabled to turn on the filter.
|
||
|
const bool resEnabled = (pIns->IsResonanceEnabled() && pIns->GetResonance() > 0 && m_sndFile.m_playBehaviour[kITFilterBehaviour]);
|
||
|
|
||
|
if((pIns->IsCutoffEnabled() && pIns->GetCutoff() < 0x7F) || resEnabled)
|
||
|
{
|
||
|
const BYTE cutoff = (resEnabled && !pIns->IsCutoffEnabled()) ? 0x7F : pIns->GetCutoff();
|
||
|
wsprintf(s, _T("Z%02X (%u Hz)"), cutoff, m_sndFile.CutOffToFrequency(cutoff));
|
||
|
} else if(pIns->IsCutoffEnabled())
|
||
|
{
|
||
|
_tcscpy(s, _T("Z7F (Off)"));
|
||
|
} else
|
||
|
{
|
||
|
_tcscpy(s, _T("No Change"));
|
||
|
}
|
||
|
|
||
|
SetDlgItemText(IDC_FILTERTEXT, s);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
bool CCtrlInstruments::OpenInstrument(const mpt::PathString &fileName)
|
||
|
{
|
||
|
BeginWaitCursor();
|
||
|
InputFile f(fileName, TrackerSettings::Instance().MiscCacheCompleteFileBeforeLoading);
|
||
|
if(!f.IsValid())
|
||
|
{
|
||
|
EndWaitCursor();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
FileReader file = GetFileReader(f);
|
||
|
|
||
|
bool first = false, ok = false;
|
||
|
if (file.IsValid())
|
||
|
{
|
||
|
if (!m_sndFile.GetNumInstruments())
|
||
|
{
|
||
|
first = true;
|
||
|
m_sndFile.m_nInstruments = 1;
|
||
|
m_modDoc.SetModified();
|
||
|
}
|
||
|
if (!m_nInstrument) m_nInstrument = 1;
|
||
|
ScopedLogCapturer log(m_modDoc, _T("Instrument Import"), this);
|
||
|
PrepareUndo("Replace Instrument");
|
||
|
if (m_sndFile.ReadInstrumentFromFile(m_nInstrument, file, TrackerSettings::Instance().m_MayNormalizeSamplesOnLoad))
|
||
|
{
|
||
|
ok = true;
|
||
|
} else
|
||
|
{
|
||
|
m_modDoc.GetInstrumentUndo().RemoveLastUndoStep(m_nInstrument);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(!ok && first)
|
||
|
{
|
||
|
// Undo adding the instrument
|
||
|
delete m_sndFile.Instruments[1];
|
||
|
m_sndFile.m_nInstruments = 0;
|
||
|
} else if(ok && first)
|
||
|
{
|
||
|
m_NoteMap.SetCurrentInstrument(1);
|
||
|
}
|
||
|
|
||
|
EndWaitCursor();
|
||
|
if(ok)
|
||
|
{
|
||
|
TrackerSettings::Instance().PathInstruments.SetWorkingDir(fileName, true);
|
||
|
ModInstrument *pIns = m_sndFile.Instruments[m_nInstrument];
|
||
|
if (pIns)
|
||
|
{
|
||
|
mpt::PathString name, ext;
|
||
|
fileName.SplitPath(nullptr, nullptr, &name, &ext);
|
||
|
|
||
|
if (!pIns->name[0] && m_sndFile.GetModSpecifications().instrNameLengthMax > 0)
|
||
|
{
|
||
|
pIns->name = mpt::truncate(name.ToLocale(), m_sndFile.GetModSpecifications().instrNameLengthMax);
|
||
|
}
|
||
|
if (!pIns->filename[0] && m_sndFile.GetModSpecifications().instrFilenameLengthMax > 0)
|
||
|
{
|
||
|
name += ext;
|
||
|
pIns->filename = mpt::truncate(name.ToLocale(), m_sndFile.GetModSpecifications().instrFilenameLengthMax);
|
||
|
}
|
||
|
|
||
|
SetCurrentInstrument(m_nInstrument);
|
||
|
InstrumentHint hint = InstrumentHint().Info().Envelope().Names();
|
||
|
if(first) hint.ModType();
|
||
|
SetModified(hint, true);
|
||
|
} else ok = FALSE;
|
||
|
} else
|
||
|
{
|
||
|
// Try loading as module
|
||
|
ok = CMainFrame::GetMainFrame()->SetTreeSoundfile(file);
|
||
|
if(ok) return true;
|
||
|
}
|
||
|
SampleHint hint = SampleHint().Info().Data().Names();
|
||
|
if (first) hint.ModType();
|
||
|
m_modDoc.UpdateAllViews(nullptr, hint);
|
||
|
if (!ok) ErrorBox(IDS_ERR_FILETYPE, this);
|
||
|
return ok;
|
||
|
}
|
||
|
|
||
|
|
||
|
bool CCtrlInstruments::OpenInstrument(const CSoundFile &sndFile, INSTRUMENTINDEX nInstr)
|
||
|
{
|
||
|
if((!nInstr) || (nInstr > sndFile.GetNumInstruments())) return false;
|
||
|
BeginWaitCursor();
|
||
|
|
||
|
CriticalSection cs;
|
||
|
|
||
|
bool first = false;
|
||
|
if (!m_sndFile.GetNumInstruments())
|
||
|
{
|
||
|
first = true;
|
||
|
m_sndFile.m_nInstruments = 1;
|
||
|
SetCurrentInstrument(1);
|
||
|
first = true;
|
||
|
}
|
||
|
PrepareUndo("Replace Instrument");
|
||
|
m_sndFile.ReadInstrumentFromSong(m_nInstrument, sndFile, nInstr);
|
||
|
|
||
|
cs.Leave();
|
||
|
|
||
|
{
|
||
|
InstrumentHint hint = InstrumentHint().Info().Envelope().Names();
|
||
|
if (first) hint.ModType();
|
||
|
SetModified(hint, true);
|
||
|
}
|
||
|
{
|
||
|
SampleHint hint = SampleHint().Info().Data().Names();
|
||
|
if (first) hint.ModType();
|
||
|
m_modDoc.UpdateAllViews(nullptr, hint, this);
|
||
|
}
|
||
|
EndWaitCursor();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL CCtrlInstruments::EditSample(UINT nSample)
|
||
|
{
|
||
|
if ((nSample > 0) && (nSample < MAX_SAMPLES))
|
||
|
{
|
||
|
m_parent.PostMessage(WM_MOD_ACTIVATEVIEW, IDD_CONTROL_SAMPLES, nSample);
|
||
|
return TRUE;
|
||
|
}
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL CCtrlInstruments::GetToolTipText(UINT uId, LPTSTR pszText)
|
||
|
{
|
||
|
//Note: pszText points to a TCHAR array of length 256 (see CChildFrame::OnToolTipText).
|
||
|
//Note2: If there's problems in getting tooltips showing for certain tools,
|
||
|
// setting the tab order may have effect.
|
||
|
ModInstrument *pIns = m_sndFile.Instruments[m_nInstrument];
|
||
|
|
||
|
if(pIns == nullptr) return FALSE;
|
||
|
if ((pszText) && (uId))
|
||
|
{
|
||
|
CWnd *wnd = GetDlgItem(uId);
|
||
|
bool isEnabled = wnd != nullptr && wnd->IsWindowEnabled() != FALSE;
|
||
|
const auto plusMinus = mpt::ToWin(mpt::Charset::UTF8, "\xC2\xB1");
|
||
|
const TCHAR *s = nullptr;
|
||
|
CommandID cmd = kcNull;
|
||
|
switch(uId)
|
||
|
{
|
||
|
case IDC_INSTRUMENT_NEW: s = _T("Insert Instrument (Hold Shift to duplicate)"); cmd = kcInstrumentNew; break;
|
||
|
case IDC_INSTRUMENT_OPEN: s = _T("Import Instrument"); cmd = kcInstrumentLoad; break;
|
||
|
case IDC_INSTRUMENT_SAVEAS: s = _T("Save Instrument"); cmd = kcInstrumentSave; break;
|
||
|
case IDC_INSTRUMENT_PLAY: s = _T("Play Instrument"); break;
|
||
|
|
||
|
case IDC_EDIT_PITCHTEMPOLOCK:
|
||
|
case IDC_CHECK_PITCHTEMPOLOCK:
|
||
|
// Pitch/Tempo lock
|
||
|
if(isEnabled)
|
||
|
{
|
||
|
const CModSpecifications& specs = m_sndFile.GetModSpecifications();
|
||
|
wsprintf(pszText, _T("Tempo Range: %u - %u"), specs.GetTempoMin().GetInt(), specs.GetTempoMax().GetInt());
|
||
|
} else
|
||
|
{
|
||
|
_tcscpy(pszText, _T("Only available in MPTM format"));
|
||
|
}
|
||
|
return TRUE;
|
||
|
|
||
|
case IDC_EDIT7:
|
||
|
// Fade Out
|
||
|
if(!pIns->nFadeOut)
|
||
|
_tcscpy(pszText, _T("Fade disabled"));
|
||
|
else
|
||
|
wsprintf(pszText, _T("%u ticks (Higher value <-> Faster fade out)"), 0x8000 / pIns->nFadeOut);
|
||
|
return TRUE;
|
||
|
|
||
|
case IDC_EDIT8:
|
||
|
// Global volume
|
||
|
if(isEnabled)
|
||
|
_tcscpy(pszText, CModDoc::LinearToDecibels(GetDlgItemInt(IDC_EDIT8), 64.0));
|
||
|
else
|
||
|
_tcscpy(pszText, _T("Only available in IT / MPTM format"));
|
||
|
return TRUE;
|
||
|
|
||
|
case IDC_EDIT9:
|
||
|
// Panning
|
||
|
if(isEnabled)
|
||
|
_tcscpy(pszText, CModDoc::PanningToString(pIns->nPan, 128));
|
||
|
else
|
||
|
_tcscpy(pszText, _T("Only available in IT / MPTM format"));
|
||
|
return TRUE;
|
||
|
|
||
|
#ifndef NO_PLUGINS
|
||
|
case IDC_EDIT10:
|
||
|
case IDC_EDIT11:
|
||
|
// Show plugin program name when hovering program or bank edits
|
||
|
if(pIns->nMixPlug > 0 && pIns->nMidiProgram != 0)
|
||
|
{
|
||
|
const SNDMIXPLUGIN &plugin = m_sndFile.m_MixPlugins[pIns->nMixPlug - 1];
|
||
|
if(plugin.pMixPlugin != nullptr)
|
||
|
{
|
||
|
int32 prog = pIns->nMidiProgram - 1;
|
||
|
if(pIns->wMidiBank > 1) prog += 128 * (pIns->wMidiBank - 1);
|
||
|
_tcscpy(pszText, plugin.pMixPlugin->GetFormattedProgramName(prog));
|
||
|
}
|
||
|
}
|
||
|
return TRUE;
|
||
|
#endif // NO_PLUGINS
|
||
|
|
||
|
case IDC_PLUGIN_VELOCITYSTYLE:
|
||
|
case IDC_PLUGIN_VOLUMESTYLE:
|
||
|
// Plugin volume handling
|
||
|
if(pIns->nMixPlug < 1) return FALSE;
|
||
|
if(m_sndFile.m_playBehaviour[kMIDICCBugEmulation])
|
||
|
{
|
||
|
velocityStyle.EnableWindow(FALSE);
|
||
|
m_CbnPluginVolumeHandling.EnableWindow(FALSE);
|
||
|
_tcscpy(pszText, _T("To enable, clear Plugin volume command bug emulation flag from Song Properties"));
|
||
|
return TRUE;
|
||
|
} else
|
||
|
{
|
||
|
if(uId == IDC_PLUGIN_VELOCITYSTYLE)
|
||
|
{
|
||
|
_tcscpy(pszText, _T("Volume commands (vxx) next to a note are sent as note velocity instead."));
|
||
|
return TRUE;
|
||
|
}
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
case IDC_COMBO5:
|
||
|
// MIDI Channel
|
||
|
s = _T("Mapped: MIDI channel corresponds to pattern channel modulo 16");
|
||
|
break;
|
||
|
|
||
|
case IDC_SLIDER1:
|
||
|
if(isEnabled)
|
||
|
wsprintf(pszText, _T("%s%d%% volume variation"), plusMinus.c_str(), pIns->nVolSwing);
|
||
|
else
|
||
|
_tcscpy(pszText, _T("Only available in IT / MPTM format"));
|
||
|
return TRUE;
|
||
|
|
||
|
case IDC_SLIDER2:
|
||
|
if(isEnabled)
|
||
|
wsprintf(pszText, _T("%s%d panning variation"), plusMinus.c_str(), pIns->nPanSwing);
|
||
|
else
|
||
|
_tcscpy(pszText, _T("Only available in IT / MPTM format"));
|
||
|
return TRUE;
|
||
|
|
||
|
case IDC_SLIDER3:
|
||
|
if(isEnabled)
|
||
|
wsprintf(pszText, _T("%u"), pIns->GetCutoff());
|
||
|
else
|
||
|
_tcscpy(pszText, _T("Only available in IT / MPTM format"));
|
||
|
return TRUE;
|
||
|
|
||
|
case IDC_SLIDER4:
|
||
|
if(isEnabled)
|
||
|
wsprintf(pszText, _T("%u (%i dB)"), pIns->GetResonance(), Util::muldivr(pIns->GetResonance(), 24, 128));
|
||
|
else
|
||
|
_tcscpy(pszText, _T("Only available in IT / MPTM format"));
|
||
|
return TRUE;
|
||
|
|
||
|
case IDC_SLIDER6:
|
||
|
if(isEnabled)
|
||
|
wsprintf(pszText, _T("%s%d cutoff variation"), plusMinus.c_str(), pIns->nCutSwing);
|
||
|
else
|
||
|
_tcscpy(pszText, _T("Only available in MPTM format"));
|
||
|
return TRUE;
|
||
|
|
||
|
case IDC_SLIDER7:
|
||
|
if(isEnabled)
|
||
|
wsprintf(pszText, _T("%s%d resonance variation"), plusMinus.c_str(), pIns->nResSwing);
|
||
|
else
|
||
|
_tcscpy(pszText, _T("Only available in MPTM format"));
|
||
|
return TRUE;
|
||
|
|
||
|
case IDC_PITCHWHEELDEPTH:
|
||
|
s = _T("Set this to the actual Pitch Wheel Depth used in your plugin on this channel.");
|
||
|
break;
|
||
|
|
||
|
case IDC_INSVIEWPLG: // Open Editor
|
||
|
if(!isEnabled)
|
||
|
s = _T("No Plugin Loaded");
|
||
|
break;
|
||
|
|
||
|
case IDC_SPIN9: // Pan
|
||
|
case IDC_CHECK1: // Pan
|
||
|
case IDC_COMBO1: // NNA
|
||
|
case IDC_COMBO2: // DCT
|
||
|
case IDC_COMBO3: // DNA
|
||
|
case IDC_COMBO4: // PPC
|
||
|
case IDC_SPIN12: // PPS
|
||
|
case IDC_EDIT15: // PPS
|
||
|
if(!isEnabled)
|
||
|
s = _T("Only available in IT / MPTM format");
|
||
|
break;
|
||
|
|
||
|
case IDC_COMBOTUNING: // Tuning
|
||
|
case IDC_COMBO9: // Resampling:
|
||
|
case IDC_SLIDER5: // Ramping
|
||
|
case IDC_SPIN1: // Ramping
|
||
|
case IDC_EDIT2: // Ramping
|
||
|
if(!isEnabled)
|
||
|
s = _T("Only available in MPTM format");
|
||
|
break;
|
||
|
|
||
|
}
|
||
|
|
||
|
if(s != nullptr)
|
||
|
{
|
||
|
_tcscpy(pszText, s);
|
||
|
if(cmd != kcNull)
|
||
|
{
|
||
|
auto keyText = CMainFrame::GetInputHandler()->m_activeCommandSet->GetKeyTextFromCommand(cmd, 0);
|
||
|
if (!keyText.IsEmpty())
|
||
|
_tcscat(pszText, MPT_TFORMAT(" ({})")(keyText).c_str());
|
||
|
}
|
||
|
return TRUE;
|
||
|
}
|
||
|
}
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////
|
||
|
// CCtrlInstruments Messages
|
||
|
|
||
|
void CCtrlInstruments::OnInstrumentChanged()
|
||
|
{
|
||
|
if(!IsLocked())
|
||
|
{
|
||
|
UINT n = GetDlgItemInt(IDC_EDIT_INSTRUMENT);
|
||
|
if ((n > 0) && (n <= m_sndFile.GetNumInstruments()) && (n != m_nInstrument))
|
||
|
{
|
||
|
SetCurrentInstrument(n, FALSE);
|
||
|
m_parent.InstrumentChanged(n);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnPrevInstrument()
|
||
|
{
|
||
|
if(m_nInstrument > 1)
|
||
|
SetCurrentInstrument(m_nInstrument - 1);
|
||
|
else
|
||
|
SetCurrentInstrument(m_sndFile.GetNumInstruments());
|
||
|
m_parent.InstrumentChanged(m_nInstrument);
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnNextInstrument()
|
||
|
{
|
||
|
if(m_nInstrument < m_sndFile.GetNumInstruments())
|
||
|
SetCurrentInstrument(m_nInstrument + 1);
|
||
|
else
|
||
|
SetCurrentInstrument(1);
|
||
|
m_parent.InstrumentChanged(m_nInstrument);
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnInstrumentNew()
|
||
|
{
|
||
|
InsertInstrument(m_sndFile.GetNumInstruments() > 0 && CMainFrame::GetInputHandler()->ShiftPressed());
|
||
|
SwitchToView();
|
||
|
}
|
||
|
|
||
|
|
||
|
bool CCtrlInstruments::InsertInstrument(bool duplicate)
|
||
|
{
|
||
|
const bool hasInstruments = m_sndFile.GetNumInstruments() > 0;
|
||
|
|
||
|
INSTRUMENTINDEX ins = m_modDoc.InsertInstrument(SAMPLEINDEX_INVALID, (duplicate && hasInstruments) ? m_nInstrument : INSTRUMENTINDEX_INVALID);
|
||
|
if (ins == INSTRUMENTINDEX_INVALID)
|
||
|
return false;
|
||
|
|
||
|
if (!hasInstruments) m_modDoc.UpdateAllViews(nullptr, InstrumentHint().Info().Names().ModType());
|
||
|
|
||
|
SetCurrentInstrument(ins);
|
||
|
m_modDoc.UpdateAllViews(nullptr, InstrumentHint(ins).Info().Envelope().Names());
|
||
|
m_parent.InstrumentChanged(m_nInstrument);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnInstrumentOpen()
|
||
|
{
|
||
|
static int nLastIndex = 0;
|
||
|
|
||
|
std::vector<FileType> mediaFoundationTypes = CSoundFile::GetMediaFoundationFileTypes();
|
||
|
FileDialog dlg = OpenFileDialog()
|
||
|
.AllowMultiSelect()
|
||
|
.EnableAudioPreview()
|
||
|
.ExtensionFilter(
|
||
|
"All Instruments (*.xi,*.pat,*.iti,*.sfz,...)|*.xi;*.pat;*.iti;*.sfz;*.flac;*.wav;*.w64;*.caf;*.aif;*.aiff;*.au;*.snd;*.sbk;*.sf2;*.sf3;*.sf4;*.dls;*.oga;*.ogg;*.opus;*.s3i;*.sb0;*.sb2;*.sbi;*.brr" + ToFilterOnlyString(mediaFoundationTypes, true).ToLocale() + "|"
|
||
|
"FastTracker II Instruments (*.xi)|*.xi|"
|
||
|
"GF1 Patches (*.pat)|*.pat|"
|
||
|
"Impulse Tracker Instruments (*.iti)|*.iti|"
|
||
|
"SFZ Instruments (*.sfz)|*.sfz|"
|
||
|
"SoundFont 2.0 Banks (*.sf2)|*.sbk;*.sf2;*.sf3;*.sf4|"
|
||
|
"DLS Sound Banks (*.dls)|*.dls|"
|
||
|
"All Files (*.*)|*.*||")
|
||
|
.WorkingDirectory(TrackerSettings::Instance().PathInstruments.GetWorkingDir())
|
||
|
.FilterIndex(&nLastIndex);
|
||
|
if(!dlg.Show(this)) return;
|
||
|
|
||
|
TrackerSettings::Instance().PathInstruments.SetWorkingDir(dlg.GetWorkingDirectory());
|
||
|
|
||
|
const FileDialog::PathList &files = dlg.GetFilenames();
|
||
|
for(size_t counter = 0; counter < files.size(); counter++)
|
||
|
{
|
||
|
//If loading multiple instruments, advancing to next instrument and creating
|
||
|
//new instrument if necessary.
|
||
|
if(counter > 0)
|
||
|
{
|
||
|
if(m_nInstrument >= MAX_INSTRUMENTS - 1)
|
||
|
break;
|
||
|
else
|
||
|
m_nInstrument++;
|
||
|
|
||
|
if(m_nInstrument > m_sndFile.GetNumInstruments())
|
||
|
OnInstrumentNew();
|
||
|
}
|
||
|
|
||
|
if(!OpenInstrument(files[counter]))
|
||
|
ErrorBox(IDS_ERR_FILEOPEN, this);
|
||
|
}
|
||
|
|
||
|
m_parent.InstrumentChanged(m_nInstrument);
|
||
|
SwitchToView();
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnInstrumentSave()
|
||
|
{
|
||
|
SaveInstrument(CMainFrame::GetInputHandler()->ShiftPressed());
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::SaveInstrument(bool doBatchSave)
|
||
|
{
|
||
|
if(!doBatchSave && m_sndFile.Instruments[m_nInstrument] == nullptr)
|
||
|
{
|
||
|
SwitchToView();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
mpt::PathString fileName;
|
||
|
if(!doBatchSave)
|
||
|
{
|
||
|
const ModInstrument *pIns = m_sndFile.Instruments[m_nInstrument];
|
||
|
if(pIns->filename[0])
|
||
|
fileName = mpt::PathString::FromLocale(pIns->filename);
|
||
|
else
|
||
|
fileName = mpt::PathString::FromLocale(pIns->name);
|
||
|
} else
|
||
|
{
|
||
|
// Save all samples
|
||
|
fileName = m_sndFile.GetpModDoc()->GetPathNameMpt().GetFileName();
|
||
|
if(fileName.empty()) fileName = P_("untitled");
|
||
|
|
||
|
fileName += P_(" - %instrument_number% - ");
|
||
|
if(m_sndFile.GetModSpecifications().sampleFilenameLengthMax == 0)
|
||
|
fileName += P_("%instrument_name%");
|
||
|
else
|
||
|
fileName += P_("%instrument_filename%");
|
||
|
|
||
|
}
|
||
|
SanitizeFilename(fileName);
|
||
|
|
||
|
int index;
|
||
|
if(TrackerSettings::Instance().compressITI)
|
||
|
index = 2;
|
||
|
else if(m_sndFile.GetType() == MOD_TYPE_XM)
|
||
|
index = 4;
|
||
|
else
|
||
|
index = 1;
|
||
|
|
||
|
FileDialog dlg = SaveFileDialog()
|
||
|
.DefaultExtension(m_sndFile.GetType() == MOD_TYPE_XM ? "xi" : "iti")
|
||
|
.DefaultFilename(fileName)
|
||
|
.ExtensionFilter(
|
||
|
"Impulse Tracker Instruments (*.iti)|*.iti|"
|
||
|
"Compressed Impulse Tracker Instruments (*.iti)|*.iti|"
|
||
|
"Impulse Tracker Instruments with external Samples (*.iti)|*.iti|"
|
||
|
"FastTracker II Instruments (*.xi)|*.xi|"
|
||
|
"SFZ Instruments with WAV (*.sfz)|*.sfz|"
|
||
|
"SFZ Instruments with FLAC (*.sfz)|*.sfz||")
|
||
|
.WorkingDirectory(TrackerSettings::Instance().PathInstruments.GetWorkingDir())
|
||
|
.FilterIndex(&index);
|
||
|
if(!dlg.Show(this)) return;
|
||
|
|
||
|
BeginWaitCursor();
|
||
|
|
||
|
INSTRUMENTINDEX minIns = m_nInstrument, maxIns = m_nInstrument;
|
||
|
if(doBatchSave)
|
||
|
{
|
||
|
minIns = 1;
|
||
|
maxIns = m_sndFile.GetNumInstruments();
|
||
|
}
|
||
|
auto numberFmt = mpt::FormatSpec().Dec().FillNul().Width(1 + static_cast<int>(std::log10(maxIns)));
|
||
|
CString instrName, instrFilename;
|
||
|
|
||
|
bool ok = true;
|
||
|
const bool saveXI = !mpt::PathString::CompareNoCase(dlg.GetExtension(), P_("xi"));
|
||
|
const bool saveSFZ = !mpt::PathString::CompareNoCase(dlg.GetExtension(), P_("sfz"));
|
||
|
const bool doCompress = index == 2 || index == 6;
|
||
|
const bool allowExternal = index == 3;
|
||
|
|
||
|
for(INSTRUMENTINDEX ins = minIns; ins <= maxIns; ins++)
|
||
|
{
|
||
|
const ModInstrument *pIns = m_sndFile.Instruments[ins];
|
||
|
if(pIns != nullptr)
|
||
|
{
|
||
|
fileName = dlg.GetFirstFile();
|
||
|
if(doBatchSave)
|
||
|
{
|
||
|
instrName = mpt::ToCString(m_sndFile.GetCharsetInternal(), pIns->name[0] ? pIns->GetName() : "untitled");
|
||
|
instrFilename = mpt::ToCString(m_sndFile.GetCharsetInternal(), pIns->filename[0] ? pIns->GetFilename() : pIns->GetName());
|
||
|
SanitizeFilename(instrName);
|
||
|
SanitizeFilename(instrFilename);
|
||
|
|
||
|
mpt::ustring fileNameW = fileName.ToUnicode();
|
||
|
fileNameW = mpt::String::Replace(fileNameW, U_("%instrument_number%"), mpt::ufmt::fmt(ins, numberFmt));
|
||
|
fileNameW = mpt::String::Replace(fileNameW, U_("%instrument_filename%"), mpt::ToUnicode(instrFilename));
|
||
|
fileNameW = mpt::String::Replace(fileNameW, U_("%instrument_name%"), mpt::ToUnicode(instrName));
|
||
|
fileName = mpt::PathString::FromUnicode(fileNameW);
|
||
|
}
|
||
|
|
||
|
try
|
||
|
{
|
||
|
ScopedLogCapturer logcapturer(m_modDoc);
|
||
|
mpt::SafeOutputFile sf(fileName, std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
|
||
|
mpt::ofstream &f = sf;
|
||
|
if(!f)
|
||
|
{
|
||
|
ok = false;
|
||
|
continue;
|
||
|
}
|
||
|
f.exceptions(f.exceptions() | std::ios::badbit | std::ios::failbit);
|
||
|
|
||
|
if (saveXI)
|
||
|
ok &= m_sndFile.SaveXIInstrument(ins, f);
|
||
|
else if (saveSFZ)
|
||
|
ok &= m_sndFile.SaveSFZInstrument(ins, f, fileName, doCompress);
|
||
|
else
|
||
|
ok &= m_sndFile.SaveITIInstrument(ins, f, fileName, doCompress, allowExternal);
|
||
|
} catch(const std::exception &)
|
||
|
{
|
||
|
ok = false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
EndWaitCursor();
|
||
|
if (!ok)
|
||
|
ErrorBox(IDS_ERR_SAVEINS, this);
|
||
|
else
|
||
|
TrackerSettings::Instance().PathInstruments.SetWorkingDir(dlg.GetWorkingDirectory());
|
||
|
SwitchToView();
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnInstrumentPlay()
|
||
|
{
|
||
|
if (m_modDoc.IsNotePlaying(NOTE_MIDDLEC, 0, m_nInstrument))
|
||
|
{
|
||
|
m_modDoc.NoteOff(NOTE_MIDDLEC, true, m_nInstrument);
|
||
|
} else
|
||
|
{
|
||
|
m_modDoc.PlayNote(PlayNoteParam(NOTE_MIDDLEC).Instrument(m_nInstrument));
|
||
|
}
|
||
|
SwitchToView();
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnNameChanged()
|
||
|
{
|
||
|
if (!IsLocked())
|
||
|
{
|
||
|
CString tmp;
|
||
|
m_EditName.GetWindowText(tmp);
|
||
|
const std::string s = mpt::ToCharset(m_sndFile.GetCharsetInternal(), tmp);
|
||
|
ModInstrument *pIns = m_sndFile.Instruments[m_nInstrument];
|
||
|
if ((pIns) && (s != pIns->name))
|
||
|
{
|
||
|
if(!m_startedEdit) PrepareUndo("Set Name");
|
||
|
pIns->name = s;
|
||
|
SetModified(InstrumentHint().Names(), false);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnFileNameChanged()
|
||
|
{
|
||
|
if (!IsLocked())
|
||
|
{
|
||
|
CString tmp;
|
||
|
m_EditFileName.GetWindowText(tmp);
|
||
|
const std::string s = mpt::ToCharset(m_sndFile.GetCharsetInternal(), tmp);
|
||
|
ModInstrument *pIns = m_sndFile.Instruments[m_nInstrument];
|
||
|
if ((pIns) && (s != pIns->filename))
|
||
|
{
|
||
|
if(!m_startedEdit) PrepareUndo("Set Filename");
|
||
|
pIns->filename = s;
|
||
|
SetModified(InstrumentHint().Names(), false);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnFadeOutVolChanged()
|
||
|
{
|
||
|
ModInstrument *pIns = m_sndFile.Instruments[m_nInstrument];
|
||
|
if ((!IsLocked()) && (pIns))
|
||
|
{
|
||
|
int minval = 0, maxval = 32767;
|
||
|
m_SpinFadeOut.GetRange(minval, maxval);
|
||
|
int nVol = GetDlgItemInt(IDC_EDIT7);
|
||
|
Limit(nVol, minval, maxval);
|
||
|
|
||
|
if(nVol != (int)pIns->nFadeOut)
|
||
|
{
|
||
|
if(!m_startedEdit) PrepareUndo("Set Fade Out");
|
||
|
pIns->nFadeOut = nVol;
|
||
|
SetModified(InstrumentHint().Info(), false);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnGlobalVolChanged()
|
||
|
{
|
||
|
ModInstrument *pIns = m_sndFile.Instruments[m_nInstrument];
|
||
|
if ((!IsLocked()) && (pIns))
|
||
|
{
|
||
|
int nVol = GetDlgItemInt(IDC_EDIT8);
|
||
|
Limit(nVol, 0, 64);
|
||
|
if (nVol != (int)pIns->nGlobalVol)
|
||
|
{
|
||
|
if(!m_startedEdit) PrepareUndo("Set Global Volume");
|
||
|
// Live-adjust volume
|
||
|
pIns->nGlobalVol = nVol;
|
||
|
for(auto &chn : m_sndFile.m_PlayState.Chn)
|
||
|
{
|
||
|
if(chn.pModInstrument == pIns)
|
||
|
{
|
||
|
chn.UpdateInstrumentVolume(chn.pModSample, pIns);
|
||
|
}
|
||
|
}
|
||
|
SetModified(InstrumentHint().Info(), false);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnSetPanningChanged()
|
||
|
{
|
||
|
ModInstrument *pIns = m_sndFile.Instruments[m_nInstrument];
|
||
|
if ((!IsLocked()) && (pIns))
|
||
|
{
|
||
|
const bool b = m_CheckPanning.GetCheck() != BST_UNCHECKED;
|
||
|
|
||
|
PrepareUndo("Toggle Panning");
|
||
|
pIns->dwFlags.set(INS_SETPANNING, b);
|
||
|
|
||
|
if(b && m_sndFile.GetType() & (MOD_TYPE_IT|MOD_TYPE_MPT))
|
||
|
{
|
||
|
bool smpPanningInUse = false;
|
||
|
|
||
|
const std::set<SAMPLEINDEX> referencedSamples = pIns->GetSamples();
|
||
|
|
||
|
for(auto sample : referencedSamples)
|
||
|
{
|
||
|
if(sample <= m_sndFile.GetNumSamples() && m_sndFile.GetSample(sample).uFlags[CHN_PANNING])
|
||
|
{
|
||
|
smpPanningInUse = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(smpPanningInUse)
|
||
|
{
|
||
|
if(Reporting::Confirm(_T("Some of the samples used in the instrument have \"Set Pan\" enabled. "
|
||
|
"Sample panning overrides instrument panning for the notes associated with such samples. "
|
||
|
"Do you wish to disable panning from those samples so that the instrument pan setting is effective "
|
||
|
"for the whole instrument?")) == cnfYes)
|
||
|
{
|
||
|
for (auto sample : referencedSamples)
|
||
|
{
|
||
|
if(sample <= m_sndFile.GetNumSamples())
|
||
|
m_sndFile.GetSample(sample).uFlags.reset(CHN_PANNING);
|
||
|
}
|
||
|
m_modDoc.UpdateAllViews(nullptr, SampleHint().Info().ModType(), this);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
SetModified(InstrumentHint().Info(), false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnPanningChanged()
|
||
|
{
|
||
|
ModInstrument *pIns = m_sndFile.Instruments[m_nInstrument];
|
||
|
if ((!IsLocked()) && (pIns))
|
||
|
{
|
||
|
int nPan = GetDlgItemInt(IDC_EDIT9);
|
||
|
if(m_modDoc.GetModType() & MOD_TYPE_IT) // IT panning ranges from 0 to 64
|
||
|
nPan *= 4;
|
||
|
if (nPan < 0) nPan = 0;
|
||
|
if (nPan > 256) nPan = 256;
|
||
|
if (nPan != (int)pIns->nPan)
|
||
|
{
|
||
|
if(!m_startedEdit) PrepareUndo("Set Panning");
|
||
|
pIns->nPan = nPan;
|
||
|
SetModified(InstrumentHint().Info(), false);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnNNAChanged()
|
||
|
{
|
||
|
ModInstrument *pIns = m_sndFile.Instruments[m_nInstrument];
|
||
|
if ((!IsLocked()) && (pIns))
|
||
|
{
|
||
|
const auto nna = static_cast<NewNoteAction>(m_ComboNNA.GetCurSel());
|
||
|
if(pIns->nNNA != nna)
|
||
|
{
|
||
|
PrepareUndo("Set New Note Action");
|
||
|
pIns->nNNA = nna;
|
||
|
SetModified(InstrumentHint().Info(), false);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnDCTChanged()
|
||
|
{
|
||
|
ModInstrument *pIns = m_sndFile.Instruments[m_nInstrument];
|
||
|
if ((!IsLocked()) && (pIns))
|
||
|
{
|
||
|
const auto dct = static_cast<DuplicateCheckType>(m_ComboDCT.GetCurSel());
|
||
|
if(pIns->nDCT != dct)
|
||
|
{
|
||
|
PrepareUndo("Set Duplicate Check Type");
|
||
|
pIns->nDCT = dct;
|
||
|
SetModified(InstrumentHint().Info(), false);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnDCAChanged()
|
||
|
{
|
||
|
ModInstrument *pIns = m_sndFile.Instruments[m_nInstrument];
|
||
|
if ((!IsLocked()) && (pIns))
|
||
|
{
|
||
|
const auto dna = static_cast<DuplicateNoteAction>(m_ComboDCA.GetCurSel());
|
||
|
if (pIns->nDNA != dna)
|
||
|
{
|
||
|
PrepareUndo("Set Duplicate Check Action");
|
||
|
pIns->nDNA = dna;
|
||
|
SetModified(InstrumentHint().Info(), false);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnMPRChanged()
|
||
|
{
|
||
|
ModInstrument *pIns = m_sndFile.Instruments[m_nInstrument];
|
||
|
if ((!IsLocked()) && (pIns))
|
||
|
{
|
||
|
int n = GetDlgItemInt(IDC_EDIT10);
|
||
|
if ((n >= 0) && (n <= 128))
|
||
|
{
|
||
|
if (pIns->nMidiProgram != n)
|
||
|
{
|
||
|
if(!m_startedEdit) PrepareUndo("Set MIDI Program");
|
||
|
pIns->nMidiProgram = static_cast<uint8>(n);
|
||
|
SetModified(InstrumentHint().Info(), false);
|
||
|
}
|
||
|
}
|
||
|
// we will not set the midi bank/program if it is 0
|
||
|
if(n == 0)
|
||
|
{
|
||
|
LockControls();
|
||
|
SetDlgItemText(IDC_EDIT10, _T("---"));
|
||
|
UnlockControls();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnMPRKillFocus()
|
||
|
{
|
||
|
ModInstrument *pIns = m_sndFile.Instruments[m_nInstrument];
|
||
|
if ((!IsLocked()) && (pIns))
|
||
|
{
|
||
|
int n = GetDlgItemInt(IDC_EDIT10);
|
||
|
if (n > 128)
|
||
|
{
|
||
|
n--;
|
||
|
pIns->nMidiProgram = static_cast<uint8>(n % 128 + 1);
|
||
|
pIns->wMidiBank = static_cast<uint16>(n / 128 + 1);
|
||
|
SetModified(InstrumentHint().Info(), false);
|
||
|
|
||
|
LockControls();
|
||
|
SetDlgItemInt(IDC_EDIT10, pIns->nMidiProgram);
|
||
|
SetDlgItemInt(IDC_EDIT11, pIns->wMidiBank);
|
||
|
UnlockControls();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnMBKChanged()
|
||
|
{
|
||
|
ModInstrument *pIns = m_sndFile.Instruments[m_nInstrument];
|
||
|
if ((!IsLocked()) && (pIns))
|
||
|
{
|
||
|
uint16 w = static_cast<uint16>(GetDlgItemInt(IDC_EDIT11));
|
||
|
if(w >= 0 && w <= 16384 && pIns->wMidiBank != w)
|
||
|
{
|
||
|
if(!m_startedEdit) PrepareUndo("Set MIDI Bank");
|
||
|
pIns->wMidiBank = w;
|
||
|
SetModified(InstrumentHint().Info(), false);
|
||
|
}
|
||
|
// we will not set the midi bank/program if it is 0
|
||
|
if(w == 0)
|
||
|
{
|
||
|
LockControls();
|
||
|
SetDlgItemText(IDC_EDIT11, _T("---"));
|
||
|
UnlockControls();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnMCHChanged()
|
||
|
{
|
||
|
ModInstrument *pIns = m_sndFile.Instruments[m_nInstrument];
|
||
|
if(!IsLocked() && pIns)
|
||
|
{
|
||
|
uint8 ch = static_cast<uint8>(m_CbnMidiCh.GetItemData(m_CbnMidiCh.GetCurSel()));
|
||
|
if(pIns->nMidiChannel != ch)
|
||
|
{
|
||
|
PrepareUndo("Set MIDI Channel");
|
||
|
pIns->nMidiChannel = ch;
|
||
|
SetModified(InstrumentHint().Info(), false);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CCtrlInstruments::OnResamplingChanged()
|
||
|
{
|
||
|
ModInstrument *pIns = m_sndFile.Instruments[m_nInstrument];
|
||
|
if ((!IsLocked()) && (pIns))
|
||
|
{
|
||
|
ResamplingMode n = static_cast<ResamplingMode>(m_CbnResampling.GetItemData(m_CbnResampling.GetCurSel()));
|
||
|
if (pIns->resampling != n)
|
||
|
{
|
||
|
PrepareUndo("Set Resampling");
|
||
|
pIns->resampling = n;
|
||
|
SetModified(InstrumentHint().Info(), false);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnMixPlugChanged()
|
||
|
{
|
||
|
ModInstrument *pIns = m_sndFile.Instruments[m_nInstrument];
|
||
|
PLUGINDEX nPlug = static_cast<PLUGINDEX>(m_CbnMixPlug.GetItemData(m_CbnMixPlug.GetCurSel()));
|
||
|
|
||
|
bool wasOpenedWithMouse = m_openendPluginListWithMouse;
|
||
|
m_openendPluginListWithMouse = false;
|
||
|
|
||
|
if (pIns)
|
||
|
{
|
||
|
BOOL enableVol = (nPlug < 1 || m_sndFile.m_playBehaviour[kMIDICCBugEmulation]) ? FALSE : TRUE;
|
||
|
velocityStyle.EnableWindow(enableVol);
|
||
|
m_CbnPluginVolumeHandling.EnableWindow(enableVol);
|
||
|
|
||
|
if(nPlug >= 0 && nPlug <= MAX_MIXPLUGINS)
|
||
|
{
|
||
|
bool active = !IsLocked();
|
||
|
if (active && pIns->nMixPlug != nPlug)
|
||
|
{
|
||
|
PrepareUndo("Set Plugin");
|
||
|
pIns->nMixPlug = nPlug;
|
||
|
SetModified(InstrumentHint().Info(), false);
|
||
|
}
|
||
|
|
||
|
velocityStyle.SetCheck(pIns->pluginVelocityHandling == PLUGIN_VELOCITYHANDLING_CHANNEL ? BST_CHECKED : BST_UNCHECKED);
|
||
|
m_CbnPluginVolumeHandling.SetCurSel(pIns->pluginVolumeHandling);
|
||
|
|
||
|
#ifndef NO_PLUGINS
|
||
|
if(pIns->nMixPlug)
|
||
|
{
|
||
|
// we have selected a plugin that's not "no plugin"
|
||
|
const SNDMIXPLUGIN &plugin = m_sndFile.m_MixPlugins[pIns->nMixPlug - 1];
|
||
|
|
||
|
if(!plugin.IsValidPlugin() && active && wasOpenedWithMouse)
|
||
|
{
|
||
|
// No plugin in this slot yet: Ask user to add one.
|
||
|
CSelectPluginDlg dlg(&m_modDoc, nPlug - 1, this);
|
||
|
if (dlg.DoModal() == IDOK)
|
||
|
{
|
||
|
if(m_sndFile.GetModSpecifications().supportsPlugins)
|
||
|
{
|
||
|
m_modDoc.SetModified();
|
||
|
}
|
||
|
UpdatePluginList();
|
||
|
|
||
|
m_modDoc.UpdateAllViews(nullptr, PluginHint(nPlug).Info().Names());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(plugin.pMixPlugin != nullptr)
|
||
|
{
|
||
|
GetDlgItem(IDC_INSVIEWPLG)->EnableWindow(true);
|
||
|
|
||
|
if(active && plugin.pMixPlugin->IsInstrument())
|
||
|
{
|
||
|
if(pIns->nMidiChannel == MidiNoChannel)
|
||
|
{
|
||
|
// If this plugin can recieve MIDI events and we have no MIDI channel
|
||
|
// selected for this instrument, automatically select MIDI channel 1.
|
||
|
pIns->nMidiChannel = MidiFirstChannel;
|
||
|
UpdateView(InstrumentHint(m_nInstrument).Info());
|
||
|
}
|
||
|
if(pIns->midiPWD == 0)
|
||
|
{
|
||
|
pIns->midiPWD = 2;
|
||
|
}
|
||
|
|
||
|
// If we just dialled up an instrument plugin, zap the sample assignments.
|
||
|
const std::set<SAMPLEINDEX> referencedSamples = pIns->GetSamples();
|
||
|
bool hasSamples = false;
|
||
|
for(auto sample : referencedSamples)
|
||
|
{
|
||
|
if(sample > 0 && sample <= m_sndFile.GetNumSamples() && m_sndFile.GetSample(sample).HasSampleData())
|
||
|
{
|
||
|
hasSamples = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(!hasSamples || Reporting::Confirm("Remove sample associations of this instrument?") == cnfYes)
|
||
|
{
|
||
|
pIns->AssignSample(0);
|
||
|
m_NoteMap.Invalidate();
|
||
|
}
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
#endif // NO_PLUGINS
|
||
|
}
|
||
|
|
||
|
}
|
||
|
::EnableWindow(::GetDlgItem(m_hWnd, IDC_INSVIEWPLG), false);
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnPPSChanged()
|
||
|
{
|
||
|
ModInstrument *pIns = m_sndFile.Instruments[m_nInstrument];
|
||
|
if ((!IsLocked()) && (pIns))
|
||
|
{
|
||
|
int n = GetDlgItemInt(IDC_EDIT15);
|
||
|
if ((n >= -32) && (n <= 32))
|
||
|
{
|
||
|
if (pIns->nPPS != (signed char)n)
|
||
|
{
|
||
|
if(!m_startedEdit) PrepareUndo("Set Pitch/Pan Separation");
|
||
|
pIns->nPPS = (signed char)n;
|
||
|
SetModified(InstrumentHint().Info(), false);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnPPCChanged()
|
||
|
{
|
||
|
ModInstrument *pIns = m_sndFile.Instruments[m_nInstrument];
|
||
|
if ((!IsLocked()) && (pIns))
|
||
|
{
|
||
|
int n = m_ComboPPC.GetCurSel();
|
||
|
if(n >= 0 && n <= NOTE_MAX - NOTE_MIN)
|
||
|
{
|
||
|
if (pIns->nPPC != n)
|
||
|
{
|
||
|
PrepareUndo("Set Pitch/Pan Center");
|
||
|
pIns->nPPC = static_cast<decltype(pIns->nPPC)>(n);
|
||
|
SetModified(InstrumentHint().Info(), false);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnAttackChanged()
|
||
|
{
|
||
|
ModInstrument *pIns = m_sndFile.Instruments[m_nInstrument];
|
||
|
if(!IsLocked() && pIns)
|
||
|
{
|
||
|
int n = Clamp(static_cast<int>(GetDlgItemInt(IDC_EDIT2)), 0, MAX_ATTACK_VALUE);
|
||
|
auto newRamp = static_cast<decltype(pIns->nVolRampUp)>(n);
|
||
|
if(pIns->nVolRampUp != newRamp)
|
||
|
{
|
||
|
if(!m_startedEdit)
|
||
|
PrepareUndo("Set Ramping");
|
||
|
pIns->nVolRampUp = newRamp;
|
||
|
SetModified(InstrumentHint().Info(), false);
|
||
|
}
|
||
|
|
||
|
m_SliderAttack.SetPos(n);
|
||
|
if(CSpinButtonCtrl *spin = (CSpinButtonCtrl *)GetDlgItem(IDC_SPIN1))
|
||
|
spin->SetPos(n);
|
||
|
LockControls();
|
||
|
if (n == 0) SetDlgItemText(IDC_EDIT2, _T("default"));
|
||
|
UnlockControls();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnEnableCutOff()
|
||
|
{
|
||
|
const bool enableCutOff = IsDlgButtonChecked(IDC_CHECK2) != BST_UNCHECKED;
|
||
|
|
||
|
ModInstrument *pIns = m_sndFile.Instruments[m_nInstrument];
|
||
|
if (pIns)
|
||
|
{
|
||
|
PrepareUndo("Toggle Cutoff");
|
||
|
pIns->SetCutoff(pIns->GetCutoff(), enableCutOff);
|
||
|
for(auto &chn : m_sndFile.m_PlayState.Chn)
|
||
|
{
|
||
|
if (chn.pModInstrument == pIns)
|
||
|
{
|
||
|
if(enableCutOff)
|
||
|
chn.nCutOff = pIns->GetCutoff();
|
||
|
else
|
||
|
chn.nCutOff = 0x7F;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
UpdateFilterText();
|
||
|
SetModified(InstrumentHint().Info(), false);
|
||
|
SwitchToView();
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnEnableResonance()
|
||
|
{
|
||
|
const bool enableReso = IsDlgButtonChecked(IDC_CHECK3) != BST_UNCHECKED;
|
||
|
|
||
|
ModInstrument *pIns = m_sndFile.Instruments[m_nInstrument];
|
||
|
if (pIns)
|
||
|
{
|
||
|
PrepareUndo("Toggle Resonance");
|
||
|
pIns->SetResonance(pIns->GetResonance(), enableReso);
|
||
|
for(auto &chn : m_sndFile.m_PlayState.Chn)
|
||
|
{
|
||
|
if (chn.pModInstrument == pIns)
|
||
|
{
|
||
|
if (enableReso)
|
||
|
chn.nResonance = pIns->GetResonance();
|
||
|
else
|
||
|
chn.nResonance = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
UpdateFilterText();
|
||
|
SetModified(InstrumentHint().Info(), false);
|
||
|
SwitchToView();
|
||
|
}
|
||
|
|
||
|
void CCtrlInstruments::OnFilterModeChanged()
|
||
|
{
|
||
|
ModInstrument *pIns = m_sndFile.Instruments[m_nInstrument];
|
||
|
if ((!IsLocked()) && (pIns))
|
||
|
{
|
||
|
FilterMode instFiltermode = static_cast<FilterMode>(m_CbnFilterMode.GetItemData(m_CbnFilterMode.GetCurSel()));
|
||
|
|
||
|
if(pIns->filterMode != instFiltermode)
|
||
|
{
|
||
|
PrepareUndo("Set Filter Mode");
|
||
|
pIns->filterMode = instFiltermode;
|
||
|
SetModified(InstrumentHint().Info(), false);
|
||
|
|
||
|
//Update channel settings where this instrument is active, if required.
|
||
|
if(instFiltermode != FilterMode::Unchanged)
|
||
|
{
|
||
|
for(auto &chn : m_sndFile.m_PlayState.Chn)
|
||
|
{
|
||
|
if(chn.pModInstrument == pIns)
|
||
|
chn.nFilterMode = instFiltermode;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnVScroll(UINT nCode, UINT nPos, CScrollBar *pSB)
|
||
|
{
|
||
|
// Give focus back to envelope editor when stopping to scroll spin buttons (for instrument preview keyboard focus)
|
||
|
CModControlDlg::OnVScroll(nCode, nPos, pSB);
|
||
|
if (nCode == SB_ENDSCROLL) SwitchToView();
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnHScroll(UINT nCode, UINT nPos, CScrollBar *pSB)
|
||
|
{
|
||
|
CModControlDlg::OnHScroll(nCode, nPos, pSB);
|
||
|
if ((m_nInstrument) && (!IsLocked()) && (nCode != SB_ENDSCROLL))
|
||
|
{
|
||
|
ModInstrument *pIns = m_sndFile.Instruments[m_nInstrument];
|
||
|
if(!pIns)
|
||
|
return;
|
||
|
|
||
|
auto *pSlider = reinterpret_cast<const CSliderCtrl *>(pSB);
|
||
|
int32 n = pSlider->GetPos();
|
||
|
bool filterChanged = false;
|
||
|
|
||
|
if(pSlider == &m_SliderAttack)
|
||
|
{
|
||
|
// Volume ramping (attack)
|
||
|
if(pIns->nVolRampUp != n)
|
||
|
{
|
||
|
if(!m_startedHScroll)
|
||
|
{
|
||
|
PrepareUndo("Set Ramping");
|
||
|
m_startedHScroll = true;
|
||
|
}
|
||
|
pIns->nVolRampUp = static_cast<decltype(pIns->nVolRampUp)>(n);
|
||
|
SetDlgItemInt(IDC_EDIT2, n);
|
||
|
SetModified(InstrumentHint().Info(), false);
|
||
|
}
|
||
|
} else if(pSlider == &m_SliderVolSwing)
|
||
|
{
|
||
|
// Volume Swing
|
||
|
if((n >= 0) && (n <= 100) && (n != pIns->nVolSwing))
|
||
|
{
|
||
|
if(!m_startedHScroll)
|
||
|
{
|
||
|
PrepareUndo("Set Volume Random Variation");
|
||
|
m_startedHScroll = true;
|
||
|
}
|
||
|
pIns->nVolSwing = static_cast<uint8>(n);
|
||
|
SetModified(InstrumentHint().Info(), false);
|
||
|
}
|
||
|
} else if(pSlider == &m_SliderPanSwing)
|
||
|
{
|
||
|
// Pan Swing
|
||
|
if((n >= 0) && (n <= 64) && (n != pIns->nPanSwing))
|
||
|
{
|
||
|
if(!m_startedHScroll)
|
||
|
{
|
||
|
PrepareUndo("Set Panning Random Variation");
|
||
|
m_startedHScroll = true;
|
||
|
}
|
||
|
pIns->nPanSwing = static_cast<uint8>(n);
|
||
|
SetModified(InstrumentHint().Info(), false);
|
||
|
}
|
||
|
} else if(pSlider == &m_SliderCutSwing)
|
||
|
{
|
||
|
// Cutoff swing
|
||
|
if((n >= 0) && (n <= 64) && (n != pIns->nCutSwing))
|
||
|
{
|
||
|
if(!m_startedHScroll)
|
||
|
{
|
||
|
PrepareUndo("Set Cutoff Random Variation");
|
||
|
m_startedHScroll = true;
|
||
|
}
|
||
|
pIns->nCutSwing = static_cast<uint8>(n);
|
||
|
SetModified(InstrumentHint().Info(), false);
|
||
|
}
|
||
|
} else if(pSlider == &m_SliderResSwing)
|
||
|
{
|
||
|
// Resonance swing
|
||
|
if((n >= 0) && (n <= 64) && (n != pIns->nResSwing))
|
||
|
{
|
||
|
if(!m_startedHScroll)
|
||
|
{
|
||
|
PrepareUndo("Set Resonance Random Variation");
|
||
|
m_startedHScroll = true;
|
||
|
}
|
||
|
pIns->nResSwing = static_cast<uint8>(n);
|
||
|
SetModified(InstrumentHint().Info(), false);
|
||
|
}
|
||
|
} else if(pSlider == &m_SliderCutOff)
|
||
|
{
|
||
|
// Filter Cutoff
|
||
|
if((n >= 0) && (n < 0x80) && (n != (int)(pIns->GetCutoff())))
|
||
|
{
|
||
|
if(!m_startedHScroll)
|
||
|
{
|
||
|
PrepareUndo("Set Cutoff");
|
||
|
m_startedHScroll = true;
|
||
|
}
|
||
|
pIns->SetCutoff(static_cast<uint8>(n), pIns->IsCutoffEnabled());
|
||
|
SetModified(InstrumentHint().Info(), false);
|
||
|
UpdateFilterText();
|
||
|
filterChanged = true;
|
||
|
}
|
||
|
} else if(pSlider == &m_SliderResonance)
|
||
|
{
|
||
|
// Filter Resonance
|
||
|
if((n >= 0) && (n < 0x80) && (n != (int)(pIns->GetResonance())))
|
||
|
{
|
||
|
if(!m_startedHScroll)
|
||
|
{
|
||
|
PrepareUndo("Set Resonance");
|
||
|
m_startedHScroll = true;
|
||
|
}
|
||
|
pIns->SetResonance(static_cast<uint8>(n), pIns->IsResonanceEnabled());
|
||
|
SetModified(InstrumentHint().Info(), false);
|
||
|
UpdateFilterText();
|
||
|
filterChanged = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Update channels
|
||
|
if(filterChanged)
|
||
|
{
|
||
|
for(auto &chn : m_sndFile.m_PlayState.Chn)
|
||
|
{
|
||
|
if(chn.pModInstrument == pIns)
|
||
|
{
|
||
|
if(pIns->IsCutoffEnabled())
|
||
|
chn.nCutOff = pIns->GetCutoff();
|
||
|
if(pIns->IsResonanceEnabled())
|
||
|
chn.nResonance = pIns->GetResonance();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
} else if(nCode == SB_ENDSCROLL)
|
||
|
{
|
||
|
m_startedHScroll = false;
|
||
|
}
|
||
|
if ((nCode == SB_ENDSCROLL) || (nCode == SB_THUMBPOSITION))
|
||
|
{
|
||
|
SwitchToView();
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnEditSampleMap()
|
||
|
{
|
||
|
if(m_nInstrument)
|
||
|
{
|
||
|
ModInstrument *pIns = m_sndFile.Instruments[m_nInstrument];
|
||
|
if (pIns)
|
||
|
{
|
||
|
PrepareUndo("Edit Sample Map");
|
||
|
CSampleMapDlg dlg(m_sndFile, m_nInstrument, this);
|
||
|
if (dlg.DoModal() == IDOK)
|
||
|
{
|
||
|
SetModified(InstrumentHint().Info(), true);
|
||
|
m_NoteMap.Invalidate(FALSE);
|
||
|
} else
|
||
|
{
|
||
|
m_modDoc.GetInstrumentUndo().RemoveLastUndoStep(m_nInstrument);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::TogglePluginEditor()
|
||
|
{
|
||
|
if(m_nInstrument)
|
||
|
{
|
||
|
m_modDoc.TogglePluginEditor(static_cast<PLUGINDEX>(m_CbnMixPlug.GetItemData(m_CbnMixPlug.GetCurSel()) - 1), CMainFrame::GetInputHandler()->ShiftPressed());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL CCtrlInstruments::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)(kCtxCtrlInstruments);
|
||
|
|
||
|
if (ih->KeyEvent(ctx, nChar, nRepCnt, nFlags, kT) != kcNull)
|
||
|
return true; // Mapped to a command, no need to pass message on.
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
return CModControlDlg::PreTranslateMessage(pMsg);
|
||
|
}
|
||
|
|
||
|
LRESULT CCtrlInstruments::OnCustomKeyMsg(WPARAM wParam, LPARAM /*lParam*/)
|
||
|
{
|
||
|
switch(wParam)
|
||
|
{
|
||
|
case kcInstrumentCtrlLoad: OnInstrumentOpen(); return wParam;
|
||
|
case kcInstrumentCtrlSave: OnInstrumentSaveOne(); return wParam;
|
||
|
case kcInstrumentCtrlNew: InsertInstrument(false); return wParam;
|
||
|
case kcInstrumentCtrlDuplicate: InsertInstrument(true); return wParam;
|
||
|
}
|
||
|
|
||
|
return kcNull;
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnCbnSelchangeCombotuning()
|
||
|
{
|
||
|
if (IsLocked()) return;
|
||
|
|
||
|
ModInstrument *instr = m_sndFile.Instruments[m_nInstrument];
|
||
|
if(instr == nullptr)
|
||
|
return;
|
||
|
|
||
|
size_t sel = m_ComboTuning.GetCurSel();
|
||
|
if(sel == 0) //Setting IT behavior
|
||
|
{
|
||
|
CriticalSection cs;
|
||
|
PrepareUndo("Reset Tuning");
|
||
|
instr->SetTuning(nullptr);
|
||
|
cs.Leave();
|
||
|
|
||
|
SetModified(InstrumentHint().Info(), true);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
sel -= 1;
|
||
|
|
||
|
if(sel < m_sndFile.GetTuneSpecificTunings().GetNumTunings())
|
||
|
{
|
||
|
CriticalSection cs;
|
||
|
PrepareUndo("Set Tuning");
|
||
|
instr->SetTuning(m_sndFile.GetTuneSpecificTunings().GetTuning(sel));
|
||
|
cs.Leave();
|
||
|
|
||
|
SetModified(InstrumentHint().Info(), true);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//Case: Chosen tuning editor to be displayed.
|
||
|
//Creating vector for the CTuningDialog.
|
||
|
CTuningDialog td(this, m_nInstrument, m_sndFile);
|
||
|
td.DoModal();
|
||
|
if(td.GetModifiedStatus(&m_sndFile.GetTuneSpecificTunings()))
|
||
|
{
|
||
|
m_modDoc.SetModified();
|
||
|
}
|
||
|
|
||
|
//Recreating tuning combobox so that possible
|
||
|
//new tuning(s) come visible.
|
||
|
BuildTuningComboBox();
|
||
|
|
||
|
m_modDoc.UpdateAllViews(nullptr, GeneralHint().Tunings());
|
||
|
m_modDoc.UpdateAllViews(nullptr, InstrumentHint().Info());
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::UpdateTuningComboBox()
|
||
|
{
|
||
|
if(m_nInstrument > m_sndFile.GetNumInstruments()
|
||
|
|| m_sndFile.Instruments[m_nInstrument] == nullptr) return;
|
||
|
|
||
|
ModInstrument* const pIns = m_sndFile.Instruments[m_nInstrument];
|
||
|
if(pIns->pTuning == nullptr)
|
||
|
{
|
||
|
m_ComboTuning.SetCurSel(0);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
for(size_t i = 0; i < m_sndFile.GetTuneSpecificTunings().GetNumTunings(); i++)
|
||
|
{
|
||
|
if(pIns->pTuning == m_sndFile.GetTuneSpecificTunings().GetTuning(i))
|
||
|
{
|
||
|
m_ComboTuning.SetCurSel((int)(i + 1));
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Reporting::Notification(MPT_CFORMAT("Tuning {} was not found. Setting to default tuning.")(mpt::ToCString(m_sndFile.Instruments[m_nInstrument]->pTuning->GetName())));
|
||
|
|
||
|
CriticalSection cs;
|
||
|
pIns->SetTuning(m_sndFile.GetDefaultTuning());
|
||
|
|
||
|
m_modDoc.SetModified();
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnPluginVelocityHandlingChanged()
|
||
|
{
|
||
|
ModInstrument *pIns = m_sndFile.Instruments[m_nInstrument];
|
||
|
if(!IsLocked() && pIns != nullptr)
|
||
|
{
|
||
|
PlugVelocityHandling n = velocityStyle.GetCheck() != BST_UNCHECKED ? PLUGIN_VELOCITYHANDLING_CHANNEL : PLUGIN_VELOCITYHANDLING_VOLUME;
|
||
|
if(n != pIns->pluginVelocityHandling)
|
||
|
{
|
||
|
PrepareUndo("Set Velocity Handling");
|
||
|
if(n == PLUGIN_VELOCITYHANDLING_VOLUME && m_CbnPluginVolumeHandling.GetCurSel() == PLUGIN_VOLUMEHANDLING_IGNORE)
|
||
|
{
|
||
|
// This combination doesn't make sense.
|
||
|
m_CbnPluginVolumeHandling.SetCurSel(PLUGIN_VOLUMEHANDLING_MIDI);
|
||
|
}
|
||
|
|
||
|
pIns->pluginVelocityHandling = n;
|
||
|
SetModified(InstrumentHint().Info(), false);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnPluginVolumeHandlingChanged()
|
||
|
{
|
||
|
ModInstrument *pIns = m_sndFile.Instruments[m_nInstrument];
|
||
|
if(!IsLocked() && pIns != nullptr)
|
||
|
{
|
||
|
PlugVolumeHandling n = static_cast<PlugVolumeHandling>(m_CbnPluginVolumeHandling.GetCurSel());
|
||
|
if(n != pIns->pluginVolumeHandling)
|
||
|
{
|
||
|
PrepareUndo("Set Volume Handling");
|
||
|
|
||
|
if(velocityStyle.GetCheck() == BST_UNCHECKED && n == PLUGIN_VOLUMEHANDLING_IGNORE)
|
||
|
{
|
||
|
// This combination doesn't make sense.
|
||
|
velocityStyle.SetCheck(BST_CHECKED);
|
||
|
}
|
||
|
|
||
|
pIns->pluginVolumeHandling = n;
|
||
|
SetModified(InstrumentHint().Info(), false);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnPitchWheelDepthChanged()
|
||
|
{
|
||
|
ModInstrument *pIns = m_sndFile.Instruments[m_nInstrument];
|
||
|
if(!IsLocked() && pIns != nullptr)
|
||
|
{
|
||
|
int pwd = GetDlgItemInt(IDC_PITCHWHEELDEPTH, NULL, TRUE);
|
||
|
int lower = -128, upper = 127;
|
||
|
m_SpinPWD.GetRange32(lower, upper);
|
||
|
Limit(pwd, lower, upper);
|
||
|
if(pwd != pIns->midiPWD)
|
||
|
{
|
||
|
if(!m_startedEdit) PrepareUndo("Set Pitch Wheel Depth");
|
||
|
pIns->midiPWD = static_cast<int8>(pwd);
|
||
|
SetModified(InstrumentHint().Info(), false);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnBnClickedCheckPitchtempolock()
|
||
|
{
|
||
|
if(IsLocked() || !m_nInstrument) return;
|
||
|
|
||
|
INSTRUMENTINDEX firstIns = m_nInstrument, lastIns = m_nInstrument;
|
||
|
if(CMainFrame::GetInputHandler()->ShiftPressed())
|
||
|
{
|
||
|
firstIns = 1;
|
||
|
lastIns = m_sndFile.GetNumInstruments();
|
||
|
}
|
||
|
|
||
|
m_EditPitchTempoLock.EnableWindow(IsDlgButtonChecked(IDC_CHECK_PITCHTEMPOLOCK));
|
||
|
TEMPO ptl(0, 0);
|
||
|
bool isZero = false;
|
||
|
if(IsDlgButtonChecked(IDC_CHECK_PITCHTEMPOLOCK))
|
||
|
{
|
||
|
//Checking what value to put for the wPitchToTempoLock.
|
||
|
if(m_EditPitchTempoLock.GetWindowTextLength() > 0)
|
||
|
{
|
||
|
ptl = m_EditPitchTempoLock.GetTempoValue();
|
||
|
}
|
||
|
if(!ptl.GetRaw())
|
||
|
{
|
||
|
ptl = m_sndFile.m_nDefaultTempo;
|
||
|
}
|
||
|
m_EditPitchTempoLock.SetTempoValue(ptl);
|
||
|
isZero = true;
|
||
|
}
|
||
|
|
||
|
for(INSTRUMENTINDEX i = firstIns; i <= lastIns; i++)
|
||
|
{
|
||
|
if(m_sndFile.Instruments[i] != nullptr && (m_sndFile.Instruments[i]->pitchToTempoLock.GetRaw() == 0) == isZero)
|
||
|
{
|
||
|
m_modDoc.GetInstrumentUndo().PrepareUndo(i, "Set Pitch/Tempo Lock");
|
||
|
m_sndFile.Instruments[i]->pitchToTempoLock = ptl;
|
||
|
m_modDoc.SetModified();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
m_modDoc.UpdateAllViews(nullptr, InstrumentHint().Info(), this);
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnEnChangeEditPitchTempoLock()
|
||
|
{
|
||
|
if(IsLocked() || !m_nInstrument || !m_sndFile.Instruments[m_nInstrument]) return;
|
||
|
|
||
|
TEMPO ptlTempo = m_EditPitchTempoLock.GetTempoValue();
|
||
|
Limit(ptlTempo, m_sndFile.GetModSpecifications().GetTempoMin(), m_sndFile.GetModSpecifications().GetTempoMax());
|
||
|
|
||
|
if(m_sndFile.Instruments[m_nInstrument]->pitchToTempoLock != ptlTempo)
|
||
|
{
|
||
|
if(!m_startedEdit) PrepareUndo("Set Pitch/Tempo Lock");
|
||
|
m_sndFile.Instruments[m_nInstrument]->pitchToTempoLock = ptlTempo;
|
||
|
m_modDoc.SetModified(); // Only update other views after killing focus
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnEnKillFocusEditPitchTempoLock()
|
||
|
{
|
||
|
//Checking that tempo value is in correct range.
|
||
|
if(IsLocked()) return;
|
||
|
|
||
|
TEMPO ptlTempo = m_EditPitchTempoLock.GetTempoValue();
|
||
|
bool changed = false;
|
||
|
const CModSpecifications& specs = m_sndFile.GetModSpecifications();
|
||
|
|
||
|
if(ptlTempo < specs.GetTempoMin())
|
||
|
{
|
||
|
ptlTempo = specs.GetTempoMin();
|
||
|
changed = true;
|
||
|
} else if(ptlTempo > specs.GetTempoMax())
|
||
|
{
|
||
|
ptlTempo = specs.GetTempoMax();
|
||
|
changed = true;
|
||
|
}
|
||
|
if(changed)
|
||
|
{
|
||
|
m_EditPitchTempoLock.SetTempoValue(ptlTempo);
|
||
|
m_modDoc.SetModified();
|
||
|
}
|
||
|
m_modDoc.UpdateAllViews(nullptr, InstrumentHint().Info(), this);
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnEnKillFocusEditFadeOut()
|
||
|
{
|
||
|
if(IsLocked() || !m_nInstrument || !m_sndFile.Instruments[m_nInstrument]) return;
|
||
|
|
||
|
if(m_modDoc.GetModType() == MOD_TYPE_IT)
|
||
|
{
|
||
|
// Coarse fade-out in IT files
|
||
|
BOOL success;
|
||
|
uint32 fadeout = (GetDlgItemInt(IDC_EDIT7, &success, FALSE) + 16) & ~31;
|
||
|
if(success && fadeout != m_sndFile.Instruments[m_nInstrument]->nFadeOut)
|
||
|
{
|
||
|
SetDlgItemInt(IDC_EDIT7, fadeout, FALSE);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::BuildTuningComboBox()
|
||
|
{
|
||
|
m_ComboTuning.SetRedraw(FALSE);
|
||
|
m_ComboTuning.ResetContent();
|
||
|
|
||
|
m_ComboTuning.AddString(_T("OpenMPT IT behaviour")); //<-> Instrument pTuning pointer == NULL
|
||
|
for(const auto &tuning : m_sndFile.GetTuneSpecificTunings())
|
||
|
{
|
||
|
m_ComboTuning.AddString(mpt::ToCString(tuning->GetName()));
|
||
|
}
|
||
|
m_ComboTuning.AddString(_T("Control Tunings..."));
|
||
|
UpdateTuningComboBox();
|
||
|
m_ComboTuning.SetRedraw(TRUE);
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::UpdatePluginList()
|
||
|
{
|
||
|
m_CbnMixPlug.SetRedraw(FALSE);
|
||
|
m_CbnMixPlug.Clear();
|
||
|
m_CbnMixPlug.ResetContent();
|
||
|
#ifndef NO_PLUGINS
|
||
|
m_CbnMixPlug.SetItemData(m_CbnMixPlug.AddString(_T("No plugin")), 0);
|
||
|
AddPluginNamesToCombobox(m_CbnMixPlug, m_sndFile.m_MixPlugins, false);
|
||
|
#endif // NO_PLUGINS
|
||
|
m_CbnMixPlug.Invalidate(FALSE);
|
||
|
m_CbnMixPlug.SetRedraw(TRUE);
|
||
|
ModInstrument *pIns = m_sndFile.Instruments[m_nInstrument];
|
||
|
if ((pIns) && (pIns->nMixPlug <= MAX_MIXPLUGINS)) m_CbnMixPlug.SetCurSel(pIns->nMixPlug);
|
||
|
}
|
||
|
|
||
|
|
||
|
void CCtrlInstruments::OnXButtonUp(UINT nFlags, UINT nButton, CPoint point)
|
||
|
{
|
||
|
if(nButton == XBUTTON1) OnPrevInstrument();
|
||
|
else if(nButton == XBUTTON2) OnNextInstrument();
|
||
|
CModControlDlg::OnXButtonUp(nFlags, nButton, point);
|
||
|
}
|
||
|
|
||
|
|
||
|
OPENMPT_NAMESPACE_END
|