/* * 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(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(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= 0) && (nPos < NOTE_MAX - NOTE_MIN + 1); if (isValidPos) { s = mpt::ToWin(sndFile.GetNoteName(static_cast(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(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(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(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(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(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(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(((n - 1) % 12) + (c - '0') * 12 + 1); else n = static_cast((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(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(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(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(FilterMode::Unchanged)); m_CbnFilterMode.SetItemData(m_CbnFilterMode.AddString(_T("Force lowpass")), static_cast(FilterMode::LowPass)); m_CbnFilterMode.SetItemData(m_CbnFilterMode.AddString(_T("Force highpass")), static_cast(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(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(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(lParam)); } UpdatePluginList(); CChildFrame *pFrame = (CChildFrame *)GetParentFrame(); INSTRUMENTVIEWSTATE &instrumentState = pFrame->GetInstrumentViewState(); if(instrumentState.initialInstrument != 0) { m_nInstrument = instrumentState.initialInstrument; instrumentState.initialInstrument = 0; } SetCurrentInstrument(static_cast((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(lParam)); break; case CTRLMSG_INS_SONGDROP: if(lParam) { const auto &dropInfo = *reinterpret_cast(lParam); if(dropInfo.sndFile) return OpenInstrument(*dropInfo.sndFile, static_cast(dropInfo.dropItem)); } break; case CTRLMSG_INS_NEWINSTRUMENT: return InsertInstrument(false) ? 1 : 0; case CTRLMSG_SETCURRENTINSTRUMENT: SetCurrentInstrument(static_cast(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().GetType()[HINT_MIXPLUGINS | HINT_PLUGINNAMES]) { OnMixPlugChanged(); } if(hint.ToType().GetType()[HINT_TUNINGS]) { BuildTuningComboBox(); } UnlockControls(); const InstrumentHint instrHint = hint.ToType(); FlagSet 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(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; resModeresampling == m_CbnResampling.GetItemData(resMode)) { m_CbnResampling.SetCurSel(resMode); break; } } for(int fltMode = 0; fltModefilterMode == static_cast(m_CbnFilterMode.GetItemData(fltMode))) { m_CbnFilterMode.SetCurSel(fltMode); break; } } // NNA, DCT, DCA m_ComboNNA.SetCurSel(static_cast(pIns->nNNA)); m_ComboDCT.SetCurSel(static_cast(pIns->nDCT)); m_ComboDCA.SetCurSel(static_cast(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().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 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(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 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(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(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(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(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(n % 128 + 1); pIns->wMidiBank = static_cast(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(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(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(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(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 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_castnPPC)>(n); SetModified(InstrumentHint().Info(), false); } } } } void CCtrlInstruments::OnAttackChanged() { ModInstrument *pIns = m_sndFile.Instruments[m_nInstrument]; if(!IsLocked() && pIns) { int n = Clamp(static_cast(GetDlgItemInt(IDC_EDIT2)), 0, MAX_ATTACK_VALUE); auto newRamp = static_castnVolRampUp)>(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(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(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_castnVolRampUp)>(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(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(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(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(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(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(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(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(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(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(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