1568 lines
56 KiB
C++
1568 lines
56 KiB
C++
|
/*
|
||
|
* dlg_misc.cpp
|
||
|
* ------------
|
||
|
* Purpose: Implementation of various OpenMPT dialogs.
|
||
|
* Notes : (currently none)
|
||
|
* Authors: OpenMPT Devs
|
||
|
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
|
||
|
*/
|
||
|
|
||
|
|
||
|
#include "stdafx.h"
|
||
|
#include "Mptrack.h"
|
||
|
#include "Moddoc.h"
|
||
|
#include "Mainfrm.h"
|
||
|
#include "dlg_misc.h"
|
||
|
#include "Dlsbank.h"
|
||
|
#include "Childfrm.h"
|
||
|
#include "../soundlib/plugins/PlugInterface.h"
|
||
|
#include "ChannelManagerDlg.h"
|
||
|
#include "TempoSwingDialog.h"
|
||
|
#include "../soundlib/mod_specifications.h"
|
||
|
#include "../common/version.h"
|
||
|
#include "../common/mptStringBuffer.h"
|
||
|
|
||
|
|
||
|
OPENMPT_NAMESPACE_BEGIN
|
||
|
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////
|
||
|
// CModTypeDlg
|
||
|
|
||
|
|
||
|
BEGIN_MESSAGE_MAP(CModTypeDlg, CDialog)
|
||
|
//{{AFX_MSG_MAP(CModTypeDlg)
|
||
|
ON_CBN_SELCHANGE(IDC_COMBO1, &CModTypeDlg::UpdateDialog)
|
||
|
ON_CBN_SELCHANGE(IDC_COMBO_TEMPOMODE, &CModTypeDlg::OnTempoModeChanged)
|
||
|
ON_COMMAND(IDC_CHECK_PT1X, &CModTypeDlg::OnPTModeChanged)
|
||
|
ON_COMMAND(IDC_BUTTON1, &CModTypeDlg::OnTempoSwing)
|
||
|
ON_COMMAND(IDC_BUTTON2, &CModTypeDlg::OnLegacyPlaybackSettings)
|
||
|
ON_COMMAND(IDC_BUTTON3, &CModTypeDlg::OnDefaultBehaviour)
|
||
|
|
||
|
ON_NOTIFY_EX(TTN_NEEDTEXT, 0, &CModTypeDlg::OnToolTipNotify)
|
||
|
|
||
|
//}}AFX_MSG_MAP
|
||
|
|
||
|
END_MESSAGE_MAP()
|
||
|
|
||
|
|
||
|
void CModTypeDlg::DoDataExchange(CDataExchange* pDX)
|
||
|
{
|
||
|
CDialog::DoDataExchange(pDX);
|
||
|
//{{AFX_DATA_MAP(CModTypeDlg)
|
||
|
DDX_Control(pDX, IDC_COMBO1, m_TypeBox);
|
||
|
DDX_Control(pDX, IDC_COMBO2, m_ChannelsBox);
|
||
|
DDX_Control(pDX, IDC_COMBO_TEMPOMODE, m_TempoModeBox);
|
||
|
DDX_Control(pDX, IDC_COMBO_MIXLEVELS, m_PlugMixBox);
|
||
|
|
||
|
DDX_Control(pDX, IDC_CHECK1, m_CheckBox1);
|
||
|
DDX_Control(pDX, IDC_CHECK2, m_CheckBox2);
|
||
|
DDX_Control(pDX, IDC_CHECK3, m_CheckBox3);
|
||
|
DDX_Control(pDX, IDC_CHECK4, m_CheckBox4);
|
||
|
DDX_Control(pDX, IDC_CHECK5, m_CheckBox5);
|
||
|
DDX_Control(pDX, IDC_CHECK_PT1X, m_CheckBoxPT1x);
|
||
|
DDX_Control(pDX, IDC_CHECK_AMIGALIMITS, m_CheckBoxAmigaLimits);
|
||
|
|
||
|
//}}AFX_DATA_MAP
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL CModTypeDlg::OnInitDialog()
|
||
|
{
|
||
|
CDialog::OnInitDialog();
|
||
|
m_nType = sndFile.GetType();
|
||
|
m_nChannels = sndFile.GetNumChannels();
|
||
|
m_tempoSwing = sndFile.m_tempoSwing;
|
||
|
m_playBehaviour = sndFile.m_playBehaviour;
|
||
|
initialized = false;
|
||
|
|
||
|
// Mod types
|
||
|
|
||
|
m_TypeBox.SetItemData(m_TypeBox.AddString(_T("ProTracker MOD")), MOD_TYPE_MOD);
|
||
|
m_TypeBox.SetItemData(m_TypeBox.AddString(_T("Scream Tracker S3M")), MOD_TYPE_S3M);
|
||
|
m_TypeBox.SetItemData(m_TypeBox.AddString(_T("FastTracker XM")), MOD_TYPE_XM);
|
||
|
m_TypeBox.SetItemData(m_TypeBox.AddString(_T("Impulse Tracker IT")), MOD_TYPE_IT);
|
||
|
m_TypeBox.SetItemData(m_TypeBox.AddString(_T("OpenMPT MPTM")), MOD_TYPE_MPT);
|
||
|
switch(m_nType)
|
||
|
{
|
||
|
case MOD_TYPE_S3M: m_TypeBox.SetCurSel(1); break;
|
||
|
case MOD_TYPE_XM: m_TypeBox.SetCurSel(2); break;
|
||
|
case MOD_TYPE_IT: m_TypeBox.SetCurSel(3); break;
|
||
|
case MOD_TYPE_MPT: m_TypeBox.SetCurSel(4); break;
|
||
|
default: m_TypeBox.SetCurSel(0); break;
|
||
|
}
|
||
|
|
||
|
// Time signature information
|
||
|
|
||
|
SetDlgItemInt(IDC_ROWSPERBEAT, sndFile.m_nDefaultRowsPerBeat);
|
||
|
SetDlgItemInt(IDC_ROWSPERMEASURE, sndFile.m_nDefaultRowsPerMeasure);
|
||
|
|
||
|
// Version information
|
||
|
|
||
|
if(sndFile.m_dwCreatedWithVersion) SetDlgItemText(IDC_EDIT_CREATEDWITH, _T("OpenMPT ") + FormatVersionNumber(sndFile.m_dwCreatedWithVersion));
|
||
|
SetDlgItemText(IDC_EDIT_SAVEDWITH, mpt::ToCString(sndFile.m_modFormat.madeWithTracker.empty() ? sndFile.m_modFormat.formatName : sndFile.m_modFormat.madeWithTracker));
|
||
|
|
||
|
const int iconSize = Util::ScalePixels(32, m_hWnd);
|
||
|
m_warnIcon = (HICON)::LoadImage(NULL, IDI_EXCLAMATION, IMAGE_ICON, iconSize, iconSize, LR_SHARED);
|
||
|
|
||
|
UpdateDialog();
|
||
|
|
||
|
initialized = true;
|
||
|
EnableToolTips(TRUE);
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
CString CModTypeDlg::FormatVersionNumber(Version version)
|
||
|
{
|
||
|
return mpt::ToCString(version.ToUString() + (version.IsTestVersion() ? U_(" (test build)") : U_("")));
|
||
|
}
|
||
|
|
||
|
|
||
|
void CModTypeDlg::UpdateChannelCBox()
|
||
|
{
|
||
|
const MODTYPE type = static_cast<MODTYPE>(m_TypeBox.GetItemData(m_TypeBox.GetCurSel()));
|
||
|
CHANNELINDEX currChanSel = static_cast<CHANNELINDEX>(m_ChannelsBox.GetItemData(m_ChannelsBox.GetCurSel()));
|
||
|
const CHANNELINDEX minChans = CSoundFile::GetModSpecifications(type).channelsMin;
|
||
|
const CHANNELINDEX maxChans = CSoundFile::GetModSpecifications(type).channelsMax;
|
||
|
|
||
|
if(m_ChannelsBox.GetCount() < 1
|
||
|
|| m_ChannelsBox.GetItemData(0) != minChans
|
||
|
|| m_ChannelsBox.GetItemData(m_ChannelsBox.GetCount() - 1) != maxChans)
|
||
|
{
|
||
|
// Update channel list if number of supported channels has changed.
|
||
|
if(m_ChannelsBox.GetCount() < 1) currChanSel = m_nChannels;
|
||
|
m_ChannelsBox.ResetContent();
|
||
|
|
||
|
CString s;
|
||
|
for(CHANNELINDEX i = minChans; i <= maxChans; i++)
|
||
|
{
|
||
|
s.Format(_T("%u Channel%s"), i, (i != 1) ? _T("s") : _T(""));
|
||
|
m_ChannelsBox.SetItemData(m_ChannelsBox.AddString(s), i);
|
||
|
}
|
||
|
|
||
|
Limit(currChanSel, minChans, maxChans);
|
||
|
m_ChannelsBox.SetCurSel(currChanSel - minChans);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CModTypeDlg::UpdateDialog()
|
||
|
{
|
||
|
m_nType = static_cast<MODTYPE>(m_TypeBox.GetItemData(m_TypeBox.GetCurSel()));
|
||
|
|
||
|
UpdateChannelCBox();
|
||
|
|
||
|
m_CheckBox1.SetCheck(sndFile.m_SongFlags[SONG_LINEARSLIDES] ? BST_CHECKED : BST_UNCHECKED);
|
||
|
m_CheckBox2.SetCheck(sndFile.m_SongFlags[SONG_FASTVOLSLIDES] ? BST_CHECKED : BST_UNCHECKED);
|
||
|
m_CheckBox3.SetCheck(sndFile.m_SongFlags[SONG_ITOLDEFFECTS] ? BST_CHECKED : BST_UNCHECKED);
|
||
|
m_CheckBox4.SetCheck(sndFile.m_SongFlags[SONG_ITCOMPATGXX] ? BST_CHECKED : BST_UNCHECKED);
|
||
|
m_CheckBox5.SetCheck(sndFile.m_SongFlags[SONG_EXFILTERRANGE] ? BST_CHECKED : BST_UNCHECKED);
|
||
|
m_CheckBoxPT1x.SetCheck(sndFile.m_SongFlags[SONG_PT_MODE] ? BST_CHECKED : BST_UNCHECKED);
|
||
|
m_CheckBoxAmigaLimits.SetCheck(sndFile.m_SongFlags[SONG_AMIGALIMITS] ? BST_CHECKED : BST_UNCHECKED);
|
||
|
|
||
|
const FlagSet<SongFlags> allowedFlags(sndFile.GetModSpecifications(m_nType).songFlags);
|
||
|
m_CheckBox1.EnableWindow(allowedFlags[SONG_LINEARSLIDES]);
|
||
|
m_CheckBox2.EnableWindow(allowedFlags[SONG_FASTVOLSLIDES]);
|
||
|
m_CheckBox3.EnableWindow(allowedFlags[SONG_ITOLDEFFECTS]);
|
||
|
m_CheckBox4.EnableWindow(allowedFlags[SONG_ITCOMPATGXX]);
|
||
|
m_CheckBox5.EnableWindow(allowedFlags[SONG_EXFILTERRANGE]);
|
||
|
m_CheckBoxPT1x.EnableWindow(allowedFlags[SONG_PT_MODE]);
|
||
|
m_CheckBoxAmigaLimits.EnableWindow(allowedFlags[SONG_AMIGALIMITS]);
|
||
|
|
||
|
// These two checkboxes are mutually exclusive and share the same screen space
|
||
|
m_CheckBoxPT1x.ShowWindow(m_nType == MOD_TYPE_MOD ? SW_SHOW : SW_HIDE);
|
||
|
m_CheckBox5.ShowWindow(m_nType != MOD_TYPE_MOD ? SW_SHOW : SW_HIDE);
|
||
|
if(allowedFlags[SONG_PT_MODE]) OnPTModeChanged();
|
||
|
|
||
|
// Tempo modes
|
||
|
const TempoMode oldTempoMode = initialized ? static_cast<TempoMode>(m_TempoModeBox.GetItemData(m_TempoModeBox.GetCurSel())) : sndFile.m_nTempoMode;
|
||
|
m_TempoModeBox.ResetContent();
|
||
|
|
||
|
m_TempoModeBox.SetItemData(m_TempoModeBox.AddString(_T("Classic")), static_cast<DWORD_PTR>(TempoMode::Classic));
|
||
|
if(m_nType == MOD_TYPE_MPT || (sndFile.GetType() != MOD_TYPE_MPT && sndFile.m_nTempoMode == TempoMode::Alternative))
|
||
|
m_TempoModeBox.SetItemData(m_TempoModeBox.AddString(_T("Alternative")), static_cast<DWORD_PTR>(TempoMode::Alternative));
|
||
|
if(m_nType == MOD_TYPE_MPT || (sndFile.GetType() != MOD_TYPE_MPT && sndFile.m_nTempoMode == TempoMode::Modern))
|
||
|
m_TempoModeBox.SetItemData(m_TempoModeBox.AddString(_T("Modern (accurate)")), static_cast<DWORD_PTR>(TempoMode::Modern));
|
||
|
m_TempoModeBox.SetCurSel(0);
|
||
|
for(int i = m_TempoModeBox.GetCount(); i > 0; i--)
|
||
|
{
|
||
|
if(static_cast<TempoMode>(m_TempoModeBox.GetItemData(i)) == oldTempoMode)
|
||
|
{
|
||
|
m_TempoModeBox.SetCurSel(i);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
OnTempoModeChanged();
|
||
|
|
||
|
// Mix levels
|
||
|
const MixLevels oldMixLevels = initialized ? static_cast<MixLevels>(m_PlugMixBox.GetItemData(m_PlugMixBox.GetCurSel())) : sndFile.GetMixLevels();
|
||
|
m_PlugMixBox.ResetContent();
|
||
|
if(m_nType == MOD_TYPE_MPT || sndFile.GetMixLevels() == MixLevels::v1_17RC3) // In XM/IT, this is only shown for backwards compatibility with existing tunes
|
||
|
m_PlugMixBox.SetItemData(m_PlugMixBox.AddString(_T("OpenMPT 1.17RC3")), static_cast<DWORD_PTR>(MixLevels::v1_17RC3));
|
||
|
if(sndFile.GetMixLevels() == MixLevels::v1_17RC2) // Only shown for backwards compatibility with existing tunes
|
||
|
m_PlugMixBox.SetItemData(m_PlugMixBox.AddString(_T("OpenMPT 1.17RC2")), static_cast<DWORD_PTR>(MixLevels::v1_17RC2));
|
||
|
if(sndFile.GetMixLevels() == MixLevels::v1_17RC1) // Ditto
|
||
|
m_PlugMixBox.SetItemData(m_PlugMixBox.AddString(_T("OpenMPT 1.17RC1")), static_cast<DWORD_PTR>(MixLevels::v1_17RC1));
|
||
|
if(sndFile.GetMixLevels() == MixLevels::Original) // Ditto
|
||
|
m_PlugMixBox.SetItemData(m_PlugMixBox.AddString(_T("Original (MPT 1.16)")), static_cast<DWORD_PTR>(MixLevels::Original));
|
||
|
int compatMixMode = m_PlugMixBox.AddString(_T("Compatible"));
|
||
|
m_PlugMixBox.SetItemData(compatMixMode, static_cast<DWORD_PTR>(MixLevels::Compatible));
|
||
|
if(m_nType == MOD_TYPE_XM)
|
||
|
m_PlugMixBox.SetItemData(m_PlugMixBox.AddString(_T("Compatible (FT2 Pan Law)")), static_cast<DWORD_PTR>(MixLevels::CompatibleFT2));
|
||
|
|
||
|
// Default to compatible mix mode
|
||
|
m_PlugMixBox.SetCurSel(compatMixMode);
|
||
|
int mixCount = m_PlugMixBox.GetCount();
|
||
|
for(int i = 0; i < mixCount; i++)
|
||
|
{
|
||
|
if(static_cast<MixLevels>(m_PlugMixBox.GetItemData(i)) == oldMixLevels)
|
||
|
{
|
||
|
m_PlugMixBox.SetCurSel(i);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const bool XMorITorMPT = (m_nType & (MOD_TYPE_XM | MOD_TYPE_IT | MOD_TYPE_MPT));
|
||
|
const bool isMPTM = (m_nType == MOD_TYPE_MPT);
|
||
|
|
||
|
// Mixmode Box
|
||
|
GetDlgItem(IDC_TEXT_MIXMODE)->EnableWindow(XMorITorMPT);
|
||
|
m_PlugMixBox.EnableWindow(XMorITorMPT);
|
||
|
|
||
|
// Tempo mode box
|
||
|
m_TempoModeBox.EnableWindow(XMorITorMPT);
|
||
|
GetDlgItem(IDC_ROWSPERBEAT)->EnableWindow(XMorITorMPT);
|
||
|
GetDlgItem(IDC_ROWSPERMEASURE)->EnableWindow(XMorITorMPT);
|
||
|
GetDlgItem(IDC_TEXT_ROWSPERBEAT)->EnableWindow(XMorITorMPT);
|
||
|
GetDlgItem(IDC_TEXT_ROWSPERMEASURE)->EnableWindow(XMorITorMPT);
|
||
|
GetDlgItem(IDC_TEXT_TEMPOMODE)->EnableWindow(XMorITorMPT);
|
||
|
GetDlgItem(IDC_FRAME_TEMPOMODE)->EnableWindow(XMorITorMPT);
|
||
|
|
||
|
// Compatibility settings
|
||
|
const PlayBehaviourSet defaultBehaviour = CSoundFile::GetDefaultPlaybackBehaviour(m_nType);
|
||
|
const PlayBehaviourSet supportedBehaviour = CSoundFile::GetSupportedPlaybackBehaviour(m_nType);
|
||
|
bool enableSetDefaults = false, showWarning = false;
|
||
|
if(m_nType & (MOD_TYPE_MPT | MOD_TYPE_IT | MOD_TYPE_XM))
|
||
|
{
|
||
|
for(size_t i = 0; i < m_playBehaviour.size(); i++)
|
||
|
{
|
||
|
// Some flags are not really important for "default" behaviour.
|
||
|
if(defaultBehaviour[i] != m_playBehaviour[i]
|
||
|
&& i != MSF_COMPATIBLE_PLAY
|
||
|
&& i != kFT2VolumeRamping)
|
||
|
{
|
||
|
enableSetDefaults = true;
|
||
|
if(!isMPTM)
|
||
|
{
|
||
|
showWarning = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if(isMPTM && m_playBehaviour[i] && !supportedBehaviour[i])
|
||
|
{
|
||
|
|
||
|
enableSetDefaults = true;
|
||
|
showWarning = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
static_cast<CStatic *>(GetDlgItem(IDC_STATIC1))->SetIcon(showWarning ? m_warnIcon : nullptr);
|
||
|
GetDlgItem(IDC_STATIC2)->SetWindowText(showWarning
|
||
|
? _T("Playback settings have been set to legacy compatibility mode. Click \"Set Defaults\" to use the recommended settings instead.")
|
||
|
: _T("Compatibility settings are currently optimal. It is advised to not edit them."));
|
||
|
GetDlgItem(IDC_BUTTON3)->EnableWindow(enableSetDefaults ? TRUE : FALSE);
|
||
|
}
|
||
|
|
||
|
|
||
|
void CModTypeDlg::OnPTModeChanged()
|
||
|
{
|
||
|
// PT1/2 mode enforces Amiga limits
|
||
|
const bool ptMode = IsDlgButtonChecked(IDC_CHECK_PT1X) != BST_UNCHECKED;
|
||
|
m_CheckBoxAmigaLimits.EnableWindow(!ptMode);
|
||
|
if(ptMode) m_CheckBoxAmigaLimits.SetCheck(BST_CHECKED);
|
||
|
}
|
||
|
|
||
|
|
||
|
void CModTypeDlg::OnTempoModeChanged()
|
||
|
{
|
||
|
GetDlgItem(IDC_BUTTON1)->EnableWindow(static_cast<TempoMode>(m_TempoModeBox.GetItemData(m_TempoModeBox.GetCurSel())) == TempoMode::Modern);
|
||
|
}
|
||
|
|
||
|
|
||
|
void CModTypeDlg::OnTempoSwing()
|
||
|
{
|
||
|
const ROWINDEX oldRPB = sndFile.m_nDefaultRowsPerBeat;
|
||
|
const ROWINDEX oldRPM = sndFile.m_nDefaultRowsPerMeasure;
|
||
|
const TempoMode oldMode = sndFile.m_nTempoMode;
|
||
|
|
||
|
// Temporarily apply new tempo signature for preview
|
||
|
const ROWINDEX newRPB = std::clamp(static_cast<ROWINDEX>(GetDlgItemInt(IDC_ROWSPERBEAT)), ROWINDEX(1), MAX_ROWS_PER_BEAT);
|
||
|
const ROWINDEX newRPM = std::clamp(static_cast<ROWINDEX>(GetDlgItemInt(IDC_ROWSPERMEASURE)), newRPB, MAX_ROWS_PER_BEAT);
|
||
|
sndFile.m_nDefaultRowsPerBeat = newRPB;
|
||
|
sndFile.m_nDefaultRowsPerMeasure = newRPM;
|
||
|
sndFile.m_nTempoMode = TempoMode::Modern;
|
||
|
|
||
|
m_tempoSwing.resize(newRPB, TempoSwing::Unity);
|
||
|
CTempoSwingDlg dlg(this, m_tempoSwing, sndFile);
|
||
|
if(dlg.DoModal() == IDOK)
|
||
|
{
|
||
|
m_tempoSwing = dlg.m_tempoSwing;
|
||
|
}
|
||
|
sndFile.m_nDefaultRowsPerBeat = oldRPB;
|
||
|
sndFile.m_nDefaultRowsPerMeasure = oldRPM;
|
||
|
sndFile.m_nTempoMode = oldMode;
|
||
|
}
|
||
|
|
||
|
|
||
|
void CModTypeDlg::OnLegacyPlaybackSettings()
|
||
|
{
|
||
|
CLegacyPlaybackSettingsDlg dlg(this, m_playBehaviour, m_nType);
|
||
|
if(dlg.DoModal() == IDOK)
|
||
|
{
|
||
|
m_playBehaviour = dlg.GetPlayBehaviour();
|
||
|
}
|
||
|
UpdateDialog();
|
||
|
}
|
||
|
|
||
|
|
||
|
void CModTypeDlg::OnDefaultBehaviour()
|
||
|
{
|
||
|
m_playBehaviour = CSoundFile::GetDefaultPlaybackBehaviour(m_nType);
|
||
|
UpdateDialog();
|
||
|
}
|
||
|
|
||
|
|
||
|
bool CModTypeDlg::VerifyData()
|
||
|
{
|
||
|
const int newRPB = GetDlgItemInt(IDC_ROWSPERBEAT);
|
||
|
const int newRPM = GetDlgItemInt(IDC_ROWSPERMEASURE);
|
||
|
if(newRPB > newRPM)
|
||
|
{
|
||
|
Reporting::Warning("Error: Rows per measure must be greater than or equal to rows per beat.");
|
||
|
GetDlgItem(IDC_ROWSPERMEASURE)->SetFocus();
|
||
|
return false;
|
||
|
}
|
||
|
if(newRPB == 0 && static_cast<TempoMode>(m_TempoModeBox.GetItemData(m_TempoModeBox.GetCurSel())) == TempoMode::Modern)
|
||
|
{
|
||
|
Reporting::Warning("Error: Rows per beat must be greater than 0 in modern tempo mode.");
|
||
|
GetDlgItem(IDC_ROWSPERBEAT)->SetFocus();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
int sel = static_cast<int>(m_ChannelsBox.GetItemData(m_ChannelsBox.GetCurSel()));
|
||
|
MODTYPE type = static_cast<MODTYPE>(m_TypeBox.GetItemData(m_TypeBox.GetCurSel()));
|
||
|
|
||
|
CHANNELINDEX maxChans = CSoundFile::GetModSpecifications(type).channelsMax;
|
||
|
|
||
|
if(sel > maxChans)
|
||
|
{
|
||
|
CString error;
|
||
|
error.Format(_T("Error: Maximum number of channels for this module type is %u."), maxChans);
|
||
|
Reporting::Warning(error);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if(maxChans < sndFile.GetNumChannels())
|
||
|
{
|
||
|
if(Reporting::Confirm("New module type supports less channels than currently used, and reducing channel number is required. Continue?") != cnfYes)
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
void CModTypeDlg::OnOK()
|
||
|
{
|
||
|
if (!VerifyData())
|
||
|
return;
|
||
|
|
||
|
int sel = m_TypeBox.GetCurSel();
|
||
|
if (sel >= 0)
|
||
|
{
|
||
|
m_nType = static_cast<MODTYPE>(m_TypeBox.GetItemData(sel));
|
||
|
}
|
||
|
const auto &newModSpecs = sndFile.GetModSpecifications(m_nType);
|
||
|
|
||
|
sndFile.m_SongFlags.set(SONG_LINEARSLIDES, m_CheckBox1.GetCheck() != BST_UNCHECKED);
|
||
|
sndFile.m_SongFlags.set(SONG_FASTVOLSLIDES, m_CheckBox2.GetCheck() != BST_UNCHECKED);
|
||
|
sndFile.m_SongFlags.set(SONG_ITOLDEFFECTS, m_CheckBox3.GetCheck() != BST_UNCHECKED);
|
||
|
sndFile.m_SongFlags.set(SONG_ITCOMPATGXX, m_CheckBox4.GetCheck() != BST_UNCHECKED);
|
||
|
sndFile.m_SongFlags.set(SONG_EXFILTERRANGE, m_CheckBox5.GetCheck() != BST_UNCHECKED);
|
||
|
sndFile.m_SongFlags.set(SONG_PT_MODE, m_CheckBoxPT1x.GetCheck() != BST_UNCHECKED);
|
||
|
sndFile.m_SongFlags.set(SONG_AMIGALIMITS, m_CheckBoxAmigaLimits.GetCheck() != BST_UNCHECKED);
|
||
|
|
||
|
sel = m_ChannelsBox.GetCurSel();
|
||
|
if (sel >= 0)
|
||
|
{
|
||
|
m_nChannels = static_cast<CHANNELINDEX>(m_ChannelsBox.GetItemData(sel));
|
||
|
}
|
||
|
|
||
|
sndFile.m_nDefaultRowsPerBeat = std::min(static_cast<ROWINDEX>(GetDlgItemInt(IDC_ROWSPERBEAT)), MAX_ROWS_PER_BEAT);
|
||
|
sndFile.m_nDefaultRowsPerMeasure = std::min(static_cast<ROWINDEX>(GetDlgItemInt(IDC_ROWSPERMEASURE)), MAX_ROWS_PER_BEAT);
|
||
|
|
||
|
sel = m_TempoModeBox.GetCurSel();
|
||
|
if(sel >= 0)
|
||
|
{
|
||
|
const auto oldMode = sndFile.m_nTempoMode;
|
||
|
sndFile.m_nTempoMode = static_cast<TempoMode>(m_TempoModeBox.GetItemData(sel));
|
||
|
if(oldMode == TempoMode::Modern && sndFile.m_nTempoMode != TempoMode::Modern)
|
||
|
{
|
||
|
double newTempo = sndFile.m_nDefaultTempo.ToDouble() * (sndFile.m_nDefaultSpeed * sndFile.m_nDefaultRowsPerBeat) / ((sndFile.m_nTempoMode == TempoMode::Classic) ? 24 : 60);
|
||
|
if(!newModSpecs.hasFractionalTempo)
|
||
|
newTempo = std::round(newTempo);
|
||
|
sndFile.m_nDefaultTempo = Clamp(TEMPO(newTempo), newModSpecs.GetTempoMin(), newModSpecs.GetTempoMax());
|
||
|
}
|
||
|
}
|
||
|
if(sndFile.m_nTempoMode == TempoMode::Modern)
|
||
|
{
|
||
|
sndFile.m_tempoSwing = m_tempoSwing;
|
||
|
if(!sndFile.m_tempoSwing.empty())
|
||
|
sndFile.m_tempoSwing.resize(sndFile.m_nDefaultRowsPerBeat);
|
||
|
} else
|
||
|
{
|
||
|
sndFile.m_tempoSwing.clear();
|
||
|
}
|
||
|
|
||
|
sel = m_PlugMixBox.GetCurSel();
|
||
|
if(sel >= 0)
|
||
|
{
|
||
|
sndFile.SetMixLevels(static_cast<MixLevels>(m_PlugMixBox.GetItemData(sel)));
|
||
|
}
|
||
|
|
||
|
PlayBehaviourSet allowedFlags = CSoundFile::GetSupportedPlaybackBehaviour(m_nType);
|
||
|
for(size_t i = 0; i < kMaxPlayBehaviours; i++)
|
||
|
{
|
||
|
// Only set those flags which are supported by the new format or were already enabled previously
|
||
|
sndFile.m_playBehaviour.set(i, m_playBehaviour[i] && (allowedFlags[i] || (sndFile.m_playBehaviour[i] && sndFile.GetType() == m_nType)));
|
||
|
}
|
||
|
|
||
|
DestroyIcon(m_warnIcon);
|
||
|
CDialog::OnOK();
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL CModTypeDlg::OnToolTipNotify(UINT, NMHDR *pNMHDR, LRESULT *)
|
||
|
{
|
||
|
TOOLTIPTEXT *pTTT = (TOOLTIPTEXT*)pNMHDR;
|
||
|
UINT_PTR nID = pNMHDR->idFrom;
|
||
|
if(pTTT->uFlags & TTF_IDISHWND)
|
||
|
{
|
||
|
// idFrom is actually the HWND of the tool
|
||
|
nID = ::GetDlgCtrlID((HWND)nID);
|
||
|
}
|
||
|
|
||
|
mpt::tstring text;
|
||
|
switch(nID)
|
||
|
{
|
||
|
case IDC_CHECK1:
|
||
|
text = _T("Note slides always slide the same amount, not depending on the sample frequency.");
|
||
|
break;
|
||
|
case IDC_CHECK2:
|
||
|
text = _T("Old Scream Tracker 3 volume slide behaviour (not recommended).");
|
||
|
break;
|
||
|
case IDC_CHECK3:
|
||
|
text = _T("Play some effects like in early versions of Impulse Tracker (not recommended).");
|
||
|
break;
|
||
|
case IDC_CHECK4:
|
||
|
text = _T("Gxx and Exx/Fxx won't share effect memory. Gxx resets instrument envelopes.");
|
||
|
break;
|
||
|
case IDC_CHECK5:
|
||
|
text = _T("The resonant filter's frequency range is increased from about 5kHz to 10kHz.");
|
||
|
break;
|
||
|
case IDC_CHECK_PT1X:
|
||
|
text = _T("Enforce Amiga frequency limits, ProTracker offset bug emulation.");
|
||
|
break;
|
||
|
case IDC_COMBO_MIXLEVELS:
|
||
|
text = _T("Mixing method of sample and instrument plugin levels.");
|
||
|
break;
|
||
|
case IDC_BUTTON1:
|
||
|
if(!GetDlgItem(IDC_BUTTON1)->IsWindowEnabled())
|
||
|
{
|
||
|
text = _T("Tempo swing is only available in modern tempo mode.");
|
||
|
} else
|
||
|
{
|
||
|
text = _T("Swing setting: ");
|
||
|
if(m_tempoSwing.empty())
|
||
|
{
|
||
|
text += _T("Default");
|
||
|
} else
|
||
|
{
|
||
|
for(size_t i = 0; i < m_tempoSwing.size(); i++)
|
||
|
{
|
||
|
if(i > 0)
|
||
|
text += _T(" / ");
|
||
|
text += MPT_TFORMAT("{}%")(Util::muldivr(m_tempoSwing[i], 100, TempoSwing::Unity));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
mpt::String::WriteWinBuf(pTTT->szText) = text;
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////////
|
||
|
// Legacy Playback Settings dialog
|
||
|
|
||
|
BEGIN_MESSAGE_MAP(CLegacyPlaybackSettingsDlg, ResizableDialog)
|
||
|
ON_COMMAND(IDC_BUTTON1, &CLegacyPlaybackSettingsDlg::OnSelectDefaults)
|
||
|
ON_EN_UPDATE(IDC_EDIT1, &CLegacyPlaybackSettingsDlg::OnFilterStringChanged)
|
||
|
ON_CLBN_CHKCHANGE(IDC_LIST1, &CLegacyPlaybackSettingsDlg::UpdateSelectDefaults)
|
||
|
END_MESSAGE_MAP()
|
||
|
|
||
|
|
||
|
void CLegacyPlaybackSettingsDlg::DoDataExchange(CDataExchange* pDX)
|
||
|
{
|
||
|
ResizableDialog::DoDataExchange(pDX);
|
||
|
DDX_Control(pDX, IDC_LIST1, m_CheckList);
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL CLegacyPlaybackSettingsDlg::OnInitDialog()
|
||
|
{
|
||
|
ResizableDialog::OnInitDialog();
|
||
|
OnFilterStringChanged();
|
||
|
UpdateSelectDefaults();
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
void CLegacyPlaybackSettingsDlg::OnSelectDefaults()
|
||
|
{
|
||
|
const int count = m_CheckList.GetCount();
|
||
|
m_playBehaviour = CSoundFile::GetDefaultPlaybackBehaviour(m_modType);
|
||
|
for(int i = 0; i < count; i++)
|
||
|
{
|
||
|
m_CheckList.SetCheck(i, m_playBehaviour[m_CheckList.GetItemData(i)] ? BST_CHECKED : BST_UNCHECKED);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CLegacyPlaybackSettingsDlg::UpdateSelectDefaults()
|
||
|
{
|
||
|
const int count = m_CheckList.GetCount();
|
||
|
for(int i = 0; i < count; i++)
|
||
|
{
|
||
|
m_playBehaviour.set(m_CheckList.GetItemData(i), m_CheckList.GetCheck(i) != BST_UNCHECKED);
|
||
|
}
|
||
|
const auto defaults = CSoundFile::GetDefaultPlaybackBehaviour(m_modType);
|
||
|
GetDlgItem(IDC_BUTTON1)->EnableWindow(m_playBehaviour != defaults ? TRUE : FALSE);
|
||
|
}
|
||
|
|
||
|
|
||
|
void CLegacyPlaybackSettingsDlg::OnFilterStringChanged()
|
||
|
{
|
||
|
CString s;
|
||
|
GetDlgItemText(IDC_EDIT1, s);
|
||
|
const bool filterActive = !s.IsEmpty();
|
||
|
s.MakeLower();
|
||
|
|
||
|
m_CheckList.SetRedraw(FALSE);
|
||
|
m_CheckList.ResetContent();
|
||
|
|
||
|
const auto allowedFlags = CSoundFile::GetSupportedPlaybackBehaviour(m_modType);
|
||
|
for(size_t i = 0; i < kMaxPlayBehaviours; i++)
|
||
|
{
|
||
|
const TCHAR *desc = _T("");
|
||
|
switch(i)
|
||
|
{
|
||
|
case MSF_COMPATIBLE_PLAY: continue;
|
||
|
|
||
|
case kMPTOldSwingBehaviour: desc = _T("OpenMPT 1.17 compatible random variation behaviour for instruments"); break;
|
||
|
case kMIDICCBugEmulation: desc = _T("Plugin volume MIDI CC bug emulation"); break;
|
||
|
case kOldMIDIPitchBends: desc = _T("Old Pitch Wheel behaviour for instrument plugins"); break;
|
||
|
case kFT2VolumeRamping: desc = _T("Use smooth Fasttracker 2 volume ramping"); break;
|
||
|
case kMODVBlankTiming: desc = _T("VBlank timing: F20 and above sets speed instead of tempo"); break;
|
||
|
|
||
|
case kSlidesAtSpeed1: desc = _T("Execute regular portamento slides at speed 1"); break;
|
||
|
case kPeriodsAreHertz: desc = _T("Compute note frequency in Hertz rather than periods"); break;
|
||
|
case kTempoClamp: desc = _T("Clamp tempo to 32-255 range"); break;
|
||
|
case kPerChannelGlobalVolSlide: desc = _T("Global volume slide memory is per-channel"); break;
|
||
|
case kPanOverride: desc = _T("Panning commands override surround and random pan variation"); break;
|
||
|
|
||
|
case kITInstrWithoutNote: desc = _T("Retrigger instrument envelopes on instrument change"); break;
|
||
|
case kITVolColFinePortamento: desc = _T("Volume column portamento never does fine portamento"); break;
|
||
|
case kITArpeggio: desc = _T("IT arpeggio algorithm"); break;
|
||
|
case kITOutOfRangeDelay: desc = _T("Out-of-range delay commands queue new instrument"); break;
|
||
|
case kITPortaMemoryShare: desc = _T("Gxx shares memory with Exx and Fxx"); break;
|
||
|
case kITPatternLoopTargetReset: desc = _T("After finishing a pattern loop, set the pattern loop target to the next row"); break;
|
||
|
case kITFT2PatternLoop: desc = _T("Nested pattern loop behaviour"); break;
|
||
|
case kITPingPongNoReset: desc = _T("Do not reset ping pong direction with instrument numbers"); break;
|
||
|
case kITEnvelopeReset: desc = _T("IT envelope reset behaviour"); break;
|
||
|
case kITClearOldNoteAfterCut: desc = _T("Forget the previous note after cutting it"); break;
|
||
|
case kITVibratoTremoloPanbrello: desc = _T("More IT-like Vibrato, Tremolo and Panbrello handling"); break;
|
||
|
case kITTremor: desc = _T("Ixx behaves like in IT"); break;
|
||
|
case kITRetrigger: desc = _T("Qxx behaves like in IT"); break;
|
||
|
case kITMultiSampleBehaviour: desc = _T("Properly update C-5 frequency when changing note in multisampled instrument"); break;
|
||
|
case kITPortaTargetReached: desc = _T("Clear portamento target after it has been reached"); break;
|
||
|
case kITPatternLoopBreak: desc = _T("Do not reset loop count on pattern break"); break;
|
||
|
case kITOffset: desc = _T("Offset after sample end is treated like in IT"); break;
|
||
|
case kITSwingBehaviour: desc = _T("Volume and panning random variation work more like in IT"); break;
|
||
|
case kITNNAReset: desc = _T("NNA is reset on every note change, not every instrument change"); break;
|
||
|
case kITSCxStopsSample: desc = _T("SCx really stops the sample and does not just mute it"); break;
|
||
|
case kITEnvelopePositionHandling: desc = _T("IT-style envelope position advance + enable/disable behaviour"); break;
|
||
|
case kITPortamentoInstrument: desc = _T("More compatible instrument change + portamento"); break;
|
||
|
case kITPingPongMode: desc = _T("Do not repeat last sample point in ping pong loop, like IT's software mixer"); break;
|
||
|
case kITRealNoteMapping: desc = _T("Use triggered note rather than translated note for PPS and DNA note check"); break;
|
||
|
case kITHighOffsetNoRetrig: desc = _T("SAx does not apply an offset effect to a note next to it"); break;
|
||
|
case kITFilterBehaviour: desc = _T("User IT's filter coefficients (unless extended filter range is used) and behaviour"); break;
|
||
|
case kITNoSurroundPan: desc = _T("Panning modulation is disabled on surround channels"); break;
|
||
|
case kITShortSampleRetrig: desc = _T("Do not retrigger already stopped channels"); break;
|
||
|
case kITPortaNoNote: desc = _T("Do not apply any portamento if no previous note is playing"); break;
|
||
|
case kITFT2DontResetNoteOffOnPorta:
|
||
|
if(m_modType == MOD_TYPE_XM)
|
||
|
desc = _T("Reset note-off on portamento if there is an instrument number");
|
||
|
else
|
||
|
desc = _T("Reset note-off on portamento if there is an instrument number in Compatible Gxx mode");
|
||
|
break;
|
||
|
case kITVolColMemory: desc = _T("Volume column effects share their memory with the effect column"); break;
|
||
|
case kITPortamentoSwapResetsPos: desc = _T("Portamento with sample swap plays the new sample from the beginning"); break;
|
||
|
case kITEmptyNoteMapSlot: desc = _T("Ignore instrument note map entries with no note completely"); break;
|
||
|
case kITFirstTickHandling: desc = _T("IT-style first tick handling"); break;
|
||
|
case kITSampleAndHoldPanbrello: desc = _T("IT-style sample&hold panbrello waveform"); break;
|
||
|
case kITClearPortaTarget: desc = _T("New notes reset portamento target in IT"); break;
|
||
|
case kITPanbrelloHold: desc = _T("Do not reset panbrello effect until next note or panning effect"); break;
|
||
|
case kITPanningReset: desc = _T("Sample and instrument panning is only applied on note change, not instrument change"); break;
|
||
|
case kITPatternLoopWithJumpsOld: desc = _T("Bxx on the same row as SBx terminates the loop in IT"); break;
|
||
|
case kITInstrWithNoteOff: desc = _T("Instrument number with note-off recalls default volume"); break;
|
||
|
case kFT2Arpeggio: desc = _T("FT2 arpeggio algorithm"); break;
|
||
|
case kFT2Retrigger: desc = _T("Rxx behaves like in FT2"); break;
|
||
|
case kFT2VolColVibrato: desc = _T("Vibrato speed in volume column does not actually execute the vibrato effect"); break;
|
||
|
case kFT2PortaNoNote: desc = _T("Do not play portamento-ed note if no previous note is playing"); break;
|
||
|
case kFT2KeyOff: desc = _T("FT2-style Kxx handling"); break;
|
||
|
case kFT2PanSlide: desc = _T("Volume-column pan slides are finer"); break;
|
||
|
case kFT2ST3OffsetOutOfRange: desc = _T("Offset past sample end stops the note"); break;
|
||
|
case kFT2RestrictXCommand: desc = _T("Do not allow ModPlug extensions to X command"); break;
|
||
|
case kFT2RetrigWithNoteDelay: desc = _T("Retrigger envelopes if there is a note delay with no note"); break;
|
||
|
case kFT2SetPanEnvPos: desc = _T("Lxx only sets the pan envelope position if the volume envelope's sustain flag is set"); break;
|
||
|
case kFT2PortaIgnoreInstr: desc = _T("Portamento with instrument number applies volume settings of new sample, but not the new sample itself"); break;
|
||
|
case kFT2VolColMemory: desc = _T("No volume column memory"); break;
|
||
|
case kFT2LoopE60Restart: desc = _T("Next pattern starts on the same row as the last E60 command"); break;
|
||
|
case kFT2ProcessSilentChannels: desc = _T("Keep processing faded channels for later portamento pickup"); break;
|
||
|
case kFT2ReloadSampleSettings: desc = _T("Reload sample settings even if a note-off is placed next to an instrument number"); break;
|
||
|
case kFT2PortaDelay: desc = _T("Portamento with note delay next to it is ignored"); break;
|
||
|
case kFT2Transpose: desc = _T("Ignore out-of-range transposed notes"); break;
|
||
|
case kFT2PatternLoopWithJumps: desc = _T("Bxx or Dxx on the same row as E6x terminates the loop"); break;
|
||
|
case kFT2PortaTargetNoReset: desc = _T("Portamento target is not reset with new notes"); break;
|
||
|
case kFT2EnvelopeEscape: desc = _T("Sustain point at end of envelope loop stops the loop after release"); break;
|
||
|
case kFT2Tremor: desc = _T("Txx behaves like in FT2"); break;
|
||
|
case kFT2OutOfRangeDelay: desc = _T("Do not trigger notes with out-of-range note delay"); break;
|
||
|
case kFT2Periods: desc = _T("Use FT2's broken period handling"); break;
|
||
|
case kFT2PanWithDelayedNoteOff: desc = _T("Panning command with delayed note-off is ignored"); break;
|
||
|
case kFT2VolColDelay: desc = _T("FT2-style volume column handling if there is a note delay"); break;
|
||
|
case kFT2FinetunePrecision: desc = _T("Round sample finetune to multiples of 8"); break;
|
||
|
case kFT2NoteOffFlags: desc = _T("Fade instrument on note-off when there is no volume envelope; instrument numbers reset note-off status"); break;
|
||
|
case kITMultiSampleInstrumentNumber: desc = _T("Lone instrument number after portamento within multi-sampled instrument sets the target sample's settings"); break;
|
||
|
case kRowDelayWithNoteDelay: desc = _T("Note delays next to a row delay are repeated on every row repetition"); break;
|
||
|
case kFT2MODTremoloRampWaveform: desc = _T("Emulate FT2/ProTracker tremolo ramp down / triangle waveform"); break;
|
||
|
case kFT2PortaUpDownMemory: desc = _T("Portamento Up and Down have separate effect memory"); break;
|
||
|
case kST3NoMutedChannels: desc = _T("Do not process any effects on muted S3M channels"); break;
|
||
|
case kST3EffectMemory: desc = _T("Most effects share the same memory"); break;
|
||
|
case kST3PortaSampleChange: desc = _T("Portamento with instrument number applies volume settings of new sample, but not the new sample itself (GUS)"); break;
|
||
|
case kST3VibratoMemory: desc = _T("Do not remember vibrato type in effect memory"); break;
|
||
|
case kST3LimitPeriod: desc = _T("ModPlug Tracker frequency limits"); break;
|
||
|
case KST3PortaAfterArpeggio: desc = _T("Portamento immediately following an arpeggio effect continues at the last arpeggiated note"); break;
|
||
|
case kMODOneShotLoops: desc = _T("ProTracker one-shot loops"); break;
|
||
|
case kMODIgnorePanning: desc = _T("Ignore panning commands"); break;
|
||
|
case kMODSampleSwap: desc = _T("Enable on-the-fly sample swapping"); break;
|
||
|
case kMODOutOfRangeNoteDelay: desc = _T("Out-of-range note delay is played on next row"); break;
|
||
|
case kMODTempoOnSecondTick: desc = _T("Tempo changes are handled on second tick instead of first"); break;
|
||
|
case kFT2PanSustainRelease: desc = _T("If the sustain point of the panning envelope is reached before key-off, it is never released"); break;
|
||
|
case kLegacyReleaseNode: desc = _T("Old volume envelope release node scaling behaviour"); break;
|
||
|
case kOPLBeatingOscillators: desc = _T("Beating OPL oscillators"); break;
|
||
|
case kST3OffsetWithoutInstrument: desc = _T("Notes without instrument use the previous note's sample offset"); break;
|
||
|
case kReleaseNodePastSustainBug: desc = _T("Broken release node after sustain end behaviour"); break;
|
||
|
case kFT2NoteDelayWithoutInstr: desc = _T("Delayed instrument-less notes should not recall volume and panning"); break;
|
||
|
case kOPLFlexibleNoteOff: desc = _T("Full control over OPL notes after note-off"); break;
|
||
|
case kITInstrWithNoteOffOldEffects: desc = _T("Instrument number with note-off retriggers envelopes with Old Effects enabled"); break;
|
||
|
case kMIDIVolumeOnNoteOffBug: desc = _T("Reset VST volume on note-off"); break;
|
||
|
case kITDoNotOverrideChannelPan: desc = _T("Instruments / samples with forced panning do not override channel panning for following instruments / samples"); break;
|
||
|
case kITPatternLoopWithJumps: desc = _T("Bxx right of SBx terminates the loop in IT"); break;
|
||
|
case kITDCTBehaviour: desc = _T("Duplicate Sample Check requires same instrument, Duplicate Note Check uses pattern notes for comparison"); break;
|
||
|
case kOPLwithNNA: desc = _T("New Note Action / Duplicate Note Action set to Note Off and Note Fade affect OPL notes like samples"); break;
|
||
|
case kST3RetrigAfterNoteCut: desc = _T("Notes cannot be retriggered after they have been cut"); break;
|
||
|
case kST3SampleSwap: desc = _T("Enable on-the-fly sample swapping (SoundBlaster driver)"); break;
|
||
|
case kOPLRealRetrig: desc = _T("Retrigger (Qxy) affects OPL notes"); break;
|
||
|
case kOPLNoResetAtEnvelopeEnd: desc = _T("Do not reset OPL channel status at end of envelopes"); break;
|
||
|
case kOPLNoteStopWith0Hz: desc = _T("OPL key-off sets note frequency to 0 Hz"); break;
|
||
|
case kOPLNoteOffOnNoteChange: desc = _T("Send OPL key-off when triggering notes"); break;
|
||
|
case kFT2PortaResetDirection: desc = _T("Tone Portamento direction resets after reaching portamento target from below"); break;
|
||
|
case kApplyUpperPeriodLimit: desc = _T("Apply lower frequency limit"); break;
|
||
|
case kApplyOffsetWithoutNote: desc = _T("Offset commands work without a note next to them"); break;
|
||
|
case kITPitchPanSeparation: desc = _T("Pitch / Pan Separation can be overridden by panning commands"); break;
|
||
|
case kImprecisePingPongLoops: desc = _T("Use old imprecise ping-pong loop end calculation"); break;
|
||
|
|
||
|
default: MPT_ASSERT_NOTREACHED();
|
||
|
}
|
||
|
|
||
|
if(filterActive && CString{desc}.MakeLower().Find(s) < 0)
|
||
|
continue;
|
||
|
|
||
|
if(m_playBehaviour[i] || allowedFlags[i])
|
||
|
{
|
||
|
int item = m_CheckList.AddString(desc);
|
||
|
m_CheckList.SetItemData(item, i);
|
||
|
int check = m_playBehaviour[i] ? BST_CHECKED : BST_UNCHECKED;
|
||
|
if(!allowedFlags[i])
|
||
|
check = BST_INDETERMINATE; // Is checked but not supported by format -> grey out
|
||
|
m_CheckList.SetCheck(item, check);
|
||
|
}
|
||
|
}
|
||
|
m_CheckList.SetRedraw(TRUE);
|
||
|
}
|
||
|
|
||
|
|
||
|
///////////////////////////////////////////////////////////
|
||
|
// CRemoveChannelsDlg
|
||
|
|
||
|
void CRemoveChannelsDlg::DoDataExchange(CDataExchange* pDX)
|
||
|
{
|
||
|
CDialog::DoDataExchange(pDX);
|
||
|
DDX_Control(pDX, IDC_REMCHANSLIST, m_RemChansList);
|
||
|
}
|
||
|
|
||
|
|
||
|
BEGIN_MESSAGE_MAP(CRemoveChannelsDlg, CDialog)
|
||
|
ON_LBN_SELCHANGE(IDC_REMCHANSLIST, &CRemoveChannelsDlg::OnChannelChanged)
|
||
|
END_MESSAGE_MAP()
|
||
|
|
||
|
|
||
|
|
||
|
BOOL CRemoveChannelsDlg::OnInitDialog()
|
||
|
{
|
||
|
CString s;
|
||
|
CDialog::OnInitDialog();
|
||
|
const CHANNELINDEX numChannels = sndFile.GetNumChannels();
|
||
|
for(CHANNELINDEX n = 0; n < numChannels; n++)
|
||
|
{
|
||
|
s = MPT_CFORMAT("Channel {}")(n + 1);
|
||
|
if(sndFile.ChnSettings[n].szName[0] >= 0x20)
|
||
|
{
|
||
|
s += _T(": ");
|
||
|
s += mpt::ToCString(sndFile.GetCharsetInternal(), sndFile.ChnSettings[n].szName);
|
||
|
}
|
||
|
m_RemChansList.SetItemData(m_RemChansList.AddString(s), n);
|
||
|
if (!m_bKeepMask[n]) m_RemChansList.SetSel(n);
|
||
|
}
|
||
|
|
||
|
if (m_nRemove > 0)
|
||
|
s = MPT_CFORMAT("Select {} channel{} to remove:")(m_nRemove, (m_nRemove != 1) ? CString(_T("s")) : CString(_T("")));
|
||
|
else
|
||
|
s = MPT_CFORMAT("Select channels to remove (the minimum number of remaining channels is {})")(sndFile.GetModSpecifications().channelsMin);
|
||
|
|
||
|
SetDlgItemText(IDC_QUESTION1, s);
|
||
|
if(GetDlgItem(IDCANCEL)) GetDlgItem(IDCANCEL)->ShowWindow(m_ShowCancel);
|
||
|
|
||
|
OnChannelChanged();
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
void CRemoveChannelsDlg::OnOK()
|
||
|
{
|
||
|
int selCount = m_RemChansList.GetSelCount();
|
||
|
std::vector<int> selected(selCount);
|
||
|
m_RemChansList.GetSelItems(selCount, selected.data());
|
||
|
|
||
|
m_bKeepMask.assign(sndFile.GetNumChannels(), true);
|
||
|
for (const auto sel : selected)
|
||
|
{
|
||
|
m_bKeepMask[sel] = false;
|
||
|
}
|
||
|
if ((static_cast<CHANNELINDEX>(selCount) == m_nRemove && selCount > 0)
|
||
|
|| (m_nRemove == 0 && (sndFile.GetNumChannels() >= selCount + sndFile.GetModSpecifications().channelsMin)))
|
||
|
CDialog::OnOK();
|
||
|
else
|
||
|
CDialog::OnCancel();
|
||
|
}
|
||
|
|
||
|
|
||
|
void CRemoveChannelsDlg::OnChannelChanged()
|
||
|
{
|
||
|
const UINT selCount = m_RemChansList.GetSelCount();
|
||
|
GetDlgItem(IDOK)->EnableWindow(((selCount == m_nRemove && selCount > 0) || (m_nRemove == 0 && (sndFile.GetNumChannels() >= selCount + sndFile.GetModSpecifications().channelsMin) && selCount > 0)) ? TRUE : FALSE);
|
||
|
}
|
||
|
|
||
|
|
||
|
InfoDialog::InfoDialog(CWnd *parent)
|
||
|
: ResizableDialog(IDD_INFO_BOX, parent)
|
||
|
{ }
|
||
|
|
||
|
BOOL InfoDialog::OnInitDialog()
|
||
|
{
|
||
|
ResizableDialog::OnInitDialog();
|
||
|
SetWindowText(m_caption.c_str());
|
||
|
SetDlgItemText(IDC_EDIT1, m_content.c_str());
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
void InfoDialog::SetContent(mpt::winstring content)
|
||
|
{
|
||
|
m_content = std::move(content);
|
||
|
}
|
||
|
|
||
|
void InfoDialog::SetCaption(mpt::winstring caption)
|
||
|
{
|
||
|
m_caption = std::move(caption);
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
// Sound Bank Information
|
||
|
|
||
|
CSoundBankProperties::CSoundBankProperties(const CDLSBank &bank, CWnd *parent)
|
||
|
: InfoDialog(parent)
|
||
|
{
|
||
|
const SOUNDBANKINFO &bi = bank.GetBankInfo();
|
||
|
std::string info;
|
||
|
info.reserve(128 + bi.szBankName.size() + bi.szDescription.size() + bi.szCopyRight.size() + bi.szEngineer.size() + bi.szSoftware.size() + bi.szComments.size());
|
||
|
info = "Type:\t" + std::string((bank.GetBankType() & SOUNDBANK_TYPE_SF2) ? "Sound Font (SF2)" : "Downloadable Sound (DLS)");
|
||
|
if (bi.szBankName.size())
|
||
|
info += "\r\nName:\t" + bi.szBankName;
|
||
|
if (bi.szDescription.size())
|
||
|
info += "\r\n\t" + bi.szDescription;
|
||
|
if (bi.szCopyRight.size())
|
||
|
info += "\r\nCopyright:\t" + bi.szCopyRight;
|
||
|
if (bi.szEngineer.size())
|
||
|
info += "\r\nAuthor:\t" + bi.szEngineer;
|
||
|
if (bi.szSoftware.size())
|
||
|
info += "\r\nSoftware:\t" + bi.szSoftware;
|
||
|
if (bi.szComments.size())
|
||
|
info += "\r\n\r\nComments:\r\n" + bi.szComments;
|
||
|
SetCaption((bank.GetFileName().AsNative() + _T(" - Sound Bank Information")));
|
||
|
SetContent(mpt::ToWin(mpt::Charset::Locale, info));
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
// Keyboard Control
|
||
|
|
||
|
static constexpr uint8 whitetab[7] = {0,2,4,5,7,9,11};
|
||
|
static constexpr uint8 blacktab[7] = {0xff,1,3,0xff,6,8,10};
|
||
|
|
||
|
BEGIN_MESSAGE_MAP(CKeyboardControl, CWnd)
|
||
|
ON_WM_DESTROY()
|
||
|
ON_WM_PAINT()
|
||
|
ON_WM_MOUSEMOVE()
|
||
|
ON_WM_LBUTTONDOWN()
|
||
|
ON_WM_LBUTTONUP()
|
||
|
END_MESSAGE_MAP()
|
||
|
|
||
|
|
||
|
void CKeyboardControl::Init(CWnd *parent, int octaves, bool cursorNotify)
|
||
|
{
|
||
|
m_parent = parent;
|
||
|
m_nOctaves = std::max(1, octaves);
|
||
|
m_cursorNotify = cursorNotify;
|
||
|
MemsetZero(KeyFlags);
|
||
|
MemsetZero(m_sampleNum);
|
||
|
|
||
|
// Point size to pixels
|
||
|
int fontSize = -MulDiv(60, Util::GetDPIy(m_hWnd), 720);
|
||
|
m_font.CreateFont(fontSize, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_RASTER_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_QUALITY, FIXED_PITCH | FF_DONTCARE, _T("MS Shell Dlg"));
|
||
|
}
|
||
|
|
||
|
|
||
|
void CKeyboardControl::OnDestroy()
|
||
|
{
|
||
|
m_font.DeleteObject();
|
||
|
}
|
||
|
|
||
|
|
||
|
void CKeyboardControl::DrawKey(CPaintDC &dc, const CRect rect, int key, bool black) const
|
||
|
{
|
||
|
const bool selected = (key == m_nSelection);
|
||
|
COLORREF color = black ? RGB(20, 20, 20) : RGB(255, 255, 255);
|
||
|
if(m_mouseDown && selected)
|
||
|
color = black ? RGB(104, 104, 104) : RGB(212, 212, 212);
|
||
|
else if(selected)
|
||
|
color = black ? RGB(130, 130, 130) : RGB(228, 228, 228);
|
||
|
dc.SetDCBrushColor(color);
|
||
|
dc.Rectangle(&rect);
|
||
|
|
||
|
if(static_cast<size_t>(key) < std::size(KeyFlags) && KeyFlags[key] != KEYFLAG_NORMAL)
|
||
|
{
|
||
|
const int margin = black ? 0 : 2;
|
||
|
CRect ellipseRect(rect.left + margin, rect.bottom - rect.Width() + margin, rect.right - margin, rect.bottom - margin);
|
||
|
dc.SetDCBrushColor((KeyFlags[key] & KEYFLAG_BRIGHTDOT) ? RGB(255, 192, 192) : RGB(255, 0, 0));
|
||
|
dc.Ellipse(ellipseRect);
|
||
|
if(m_sampleNum[key] != 0)
|
||
|
{
|
||
|
dc.SetTextColor((KeyFlags[key] & KEYFLAG_BRIGHTDOT) ? RGB(0, 0, 0) : RGB(255, 255, 255));
|
||
|
TCHAR s[16];
|
||
|
wsprintf(s, _T("%u"), m_sampleNum[key]);
|
||
|
dc.DrawText(s, -1, ellipseRect, DT_CENTER | DT_SINGLELINE | DT_VCENTER);
|
||
|
}
|
||
|
|
||
|
if(KeyFlags[key] == (KEYFLAG_REDDOT | KEYFLAG_BRIGHTDOT))
|
||
|
{
|
||
|
// Both flags set: Draw second dot
|
||
|
ellipseRect.MoveToY(ellipseRect.top - ellipseRect.Height() - 2);
|
||
|
dc.SetDCBrushColor(RGB(255, 0, 0));
|
||
|
dc.Ellipse(ellipseRect);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CKeyboardControl::OnPaint()
|
||
|
{
|
||
|
CRect rcClient, rect;
|
||
|
CPaintDC dc(this);
|
||
|
|
||
|
dc.SetBkMode(TRANSPARENT);
|
||
|
GetClientRect(&rcClient);
|
||
|
rect = rcClient;
|
||
|
auto oldBrush = dc.SelectObject(GetStockObject(DC_BRUSH));
|
||
|
auto oldPen = dc.SelectObject(GetStockObject(DC_PEN));
|
||
|
auto oldFont = dc.SelectObject(&m_font);
|
||
|
|
||
|
// Rectangle outline
|
||
|
dc.SetDCPenColor(RGB(50, 50, 50));
|
||
|
|
||
|
// White notes
|
||
|
for(int note = 0; note < m_nOctaves * 7; note++)
|
||
|
{
|
||
|
rect.right = ((note + 1) * rcClient.Width()) / (m_nOctaves * 7);
|
||
|
int val = (note / 7) * 12 + whitetab[note % 7];
|
||
|
|
||
|
DrawKey(dc, rect, val, false);
|
||
|
|
||
|
rect.left = rect.right - 1;
|
||
|
}
|
||
|
|
||
|
// Black notes
|
||
|
rect = rcClient;
|
||
|
rect.bottom -= rcClient.Height() / 3;
|
||
|
for(int note = 0; note < m_nOctaves * 7; note++)
|
||
|
{
|
||
|
switch(note % 7)
|
||
|
{
|
||
|
case 1:
|
||
|
case 2:
|
||
|
case 4:
|
||
|
case 5:
|
||
|
case 6:
|
||
|
{
|
||
|
rect.left = (note * rcClient.Width()) / (m_nOctaves * 7);
|
||
|
rect.right = rect.left;
|
||
|
int delta = rcClient.Width() / (m_nOctaves * 7 * 3);
|
||
|
rect.left -= delta;
|
||
|
rect.right += delta;
|
||
|
int val = (note / 7) * 12 + blacktab[note % 7];
|
||
|
|
||
|
DrawKey(dc, rect, val, true);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
dc.SelectObject(oldBrush);
|
||
|
dc.SelectObject(oldPen);
|
||
|
dc.SelectObject(oldFont);
|
||
|
}
|
||
|
|
||
|
|
||
|
void CKeyboardControl::OnMouseMove(UINT flags, CPoint point)
|
||
|
{
|
||
|
CRect rcClient, rect;
|
||
|
GetClientRect(&rcClient);
|
||
|
rect = rcClient;
|
||
|
int xmin = rcClient.right;
|
||
|
int xmax = rcClient.left;
|
||
|
int sel = -1;
|
||
|
// White notes
|
||
|
for(int note = 0; note < m_nOctaves * 7; note++)
|
||
|
{
|
||
|
int val = (note / 7) * 12 + whitetab[note % 7];
|
||
|
rect.right = ((note + 1) * rcClient.Width()) / (m_nOctaves * 7);
|
||
|
if (val == m_nSelection)
|
||
|
{
|
||
|
if (rect.left < xmin) xmin = rect.left;
|
||
|
if (rect.right > xmax) xmax = rect.right;
|
||
|
}
|
||
|
if (rect.PtInRect(point))
|
||
|
{
|
||
|
sel = val;
|
||
|
if (rect.left < xmin) xmin = rect.left;
|
||
|
if (rect.right > xmax) xmax = rect.right;
|
||
|
}
|
||
|
rect.left = rect.right - 1;
|
||
|
}
|
||
|
// Black notes
|
||
|
rect = rcClient;
|
||
|
rect.bottom -= rcClient.Height() / 3;
|
||
|
for(int note = 0; note < m_nOctaves * 7; note++)
|
||
|
{
|
||
|
switch(note % 7)
|
||
|
{
|
||
|
case 1:
|
||
|
case 2:
|
||
|
case 4:
|
||
|
case 5:
|
||
|
case 6:
|
||
|
{
|
||
|
int val = (note / 7) * 12 + blacktab[note % 7];
|
||
|
rect.left = (note * rcClient.Width()) / (m_nOctaves * 7);
|
||
|
rect.right = rect.left;
|
||
|
int delta = rcClient.Width() / (m_nOctaves * 7 * 3);
|
||
|
rect.left -= delta;
|
||
|
rect.right += delta;
|
||
|
if(val == m_nSelection)
|
||
|
{
|
||
|
if(rect.left < xmin)
|
||
|
xmin = rect.left;
|
||
|
if(rect.right > xmax)
|
||
|
xmax = rect.right;
|
||
|
}
|
||
|
if(rect.PtInRect(point))
|
||
|
{
|
||
|
sel = val;
|
||
|
if(rect.left < xmin)
|
||
|
xmin = rect.left;
|
||
|
if(rect.right > xmax)
|
||
|
xmax = rect.right;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// Check for selection change
|
||
|
if(sel != m_nSelection)
|
||
|
{
|
||
|
m_nSelection = sel;
|
||
|
rcClient.left = xmin;
|
||
|
rcClient.right = xmax;
|
||
|
InvalidateRect(&rcClient, FALSE);
|
||
|
if(m_cursorNotify && m_parent)
|
||
|
{
|
||
|
m_parent->PostMessage(WM_MOD_KBDNOTIFY, KBDNOTIFY_MOUSEMOVE, m_nSelection);
|
||
|
if(flags & MK_LBUTTON)
|
||
|
m_parent->SendMessage(WM_MOD_KBDNOTIFY, KBDNOTIFY_LBUTTONDOWN, m_nSelection);
|
||
|
}
|
||
|
}
|
||
|
if(sel >= 0)
|
||
|
{
|
||
|
if(!m_mouseCapture)
|
||
|
{
|
||
|
m_mouseCapture = true;
|
||
|
SetCapture();
|
||
|
}
|
||
|
} else
|
||
|
{
|
||
|
if(m_mouseCapture)
|
||
|
{
|
||
|
m_mouseCapture = false;
|
||
|
ReleaseCapture();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void CKeyboardControl::OnLButtonDown(UINT, CPoint)
|
||
|
{
|
||
|
m_mouseDown = true;
|
||
|
InvalidateRect(nullptr, FALSE);
|
||
|
if(m_parent)
|
||
|
m_parent->SendMessage(WM_MOD_KBDNOTIFY, KBDNOTIFY_LBUTTONDOWN, m_nSelection);
|
||
|
}
|
||
|
|
||
|
|
||
|
void CKeyboardControl::OnLButtonUp(UINT, CPoint)
|
||
|
{
|
||
|
m_mouseDown = false;
|
||
|
InvalidateRect(nullptr, FALSE);
|
||
|
if(m_parent)
|
||
|
m_parent->SendMessage(WM_MOD_KBDNOTIFY, KBDNOTIFY_LBUTTONUP, m_nSelection);
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Sample Map
|
||
|
//
|
||
|
|
||
|
BEGIN_MESSAGE_MAP(CSampleMapDlg, CDialog)
|
||
|
ON_MESSAGE(WM_MOD_KBDNOTIFY, &CSampleMapDlg::OnKeyboardNotify)
|
||
|
ON_WM_HSCROLL()
|
||
|
ON_COMMAND(IDC_CHECK1, &CSampleMapDlg::OnUpdateSamples)
|
||
|
ON_CBN_SELCHANGE(IDC_COMBO1, &CSampleMapDlg::OnUpdateKeyboard)
|
||
|
END_MESSAGE_MAP()
|
||
|
|
||
|
void CSampleMapDlg::DoDataExchange(CDataExchange* pDX)
|
||
|
{
|
||
|
CDialog::DoDataExchange(pDX);
|
||
|
//{{AFX_DATA_MAP(CSampleMapDlg)
|
||
|
DDX_Control(pDX, IDC_KEYBOARD1, m_Keyboard);
|
||
|
DDX_Control(pDX, IDC_COMBO1, m_CbnSample);
|
||
|
DDX_Control(pDX, IDC_SLIDER1, m_SbOctave);
|
||
|
//}}AFX_DATA_MAP
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL CSampleMapDlg::OnInitDialog()
|
||
|
{
|
||
|
CDialog::OnInitDialog();
|
||
|
ModInstrument *pIns = sndFile.Instruments[m_nInstrument];
|
||
|
if(pIns)
|
||
|
{
|
||
|
for(UINT i = 0; i < NOTE_MAX; i++)
|
||
|
{
|
||
|
KeyboardMap[i] = pIns->Keyboard[i];
|
||
|
}
|
||
|
}
|
||
|
m_Keyboard.Init(this, 3, TRUE);
|
||
|
m_SbOctave.SetRange(0, 7);
|
||
|
m_SbOctave.SetPos(4);
|
||
|
OnUpdateSamples();
|
||
|
OnUpdateOctave();
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
void CSampleMapDlg::OnHScroll(UINT nCode, UINT nPos, CScrollBar *pBar)
|
||
|
{
|
||
|
CDialog::OnHScroll(nCode, nPos, pBar);
|
||
|
OnUpdateKeyboard();
|
||
|
OnUpdateOctave();
|
||
|
}
|
||
|
|
||
|
|
||
|
void CSampleMapDlg::OnUpdateSamples()
|
||
|
{
|
||
|
UINT oldPos = 0;
|
||
|
UINT newPos = 0;
|
||
|
|
||
|
if(m_nInstrument >= MAX_INSTRUMENTS)
|
||
|
return;
|
||
|
if(m_CbnSample.GetCount() > 0)
|
||
|
oldPos = static_cast<UINT>(m_CbnSample.GetItemData(m_CbnSample.GetCurSel()));
|
||
|
m_CbnSample.SetRedraw(FALSE);
|
||
|
m_CbnSample.ResetContent();
|
||
|
const bool showAll = (IsDlgButtonChecked(IDC_CHECK1) != FALSE) || (*std::max_element(std::begin(KeyboardMap), std::end(KeyboardMap)) == 0);
|
||
|
|
||
|
UINT insertPos = m_CbnSample.AddString(_T("0: No sample"));
|
||
|
m_CbnSample.SetItemData(insertPos, 0);
|
||
|
|
||
|
for(SAMPLEINDEX i = 1; i <= sndFile.GetNumSamples(); i++)
|
||
|
{
|
||
|
bool isUsed = showAll || mpt::contains(KeyboardMap, i);
|
||
|
if(isUsed)
|
||
|
{
|
||
|
CString sampleName;
|
||
|
sampleName.Format(_T("%d: %s"), i, mpt::ToCString(sndFile.GetCharsetInternal(), sndFile.GetSampleName(i)).GetString());
|
||
|
insertPos = m_CbnSample.AddString(sampleName);
|
||
|
|
||
|
m_CbnSample.SetItemData(insertPos, i);
|
||
|
if(i == oldPos)
|
||
|
newPos = insertPos;
|
||
|
}
|
||
|
}
|
||
|
m_CbnSample.SetRedraw(TRUE);
|
||
|
m_CbnSample.SetCurSel(newPos);
|
||
|
OnUpdateKeyboard();
|
||
|
}
|
||
|
|
||
|
|
||
|
void CSampleMapDlg::OnUpdateOctave()
|
||
|
{
|
||
|
TCHAR s[64];
|
||
|
const UINT baseOctave = m_SbOctave.GetPos() & 7;
|
||
|
wsprintf(s, _T("Octaves %u-%u"), baseOctave, baseOctave + 2);
|
||
|
SetDlgItemText(IDC_TEXT1, s);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
void CSampleMapDlg::OnUpdateKeyboard()
|
||
|
{
|
||
|
SAMPLEINDEX nSample = static_cast<SAMPLEINDEX>(m_CbnSample.GetItemData(m_CbnSample.GetCurSel()));
|
||
|
const UINT baseOctave = m_SbOctave.GetPos() & 7;
|
||
|
bool redraw = false;
|
||
|
for(UINT iNote = 0; iNote < 3 * 12; iNote++)
|
||
|
{
|
||
|
uint8 oldFlags = m_Keyboard.GetFlags(iNote);
|
||
|
SAMPLEINDEX oldSmp = m_Keyboard.GetSample(iNote);
|
||
|
UINT ndx = baseOctave * 12 + iNote;
|
||
|
uint8 newFlags = CKeyboardControl::KEYFLAG_NORMAL;
|
||
|
if(KeyboardMap[ndx] == nSample)
|
||
|
newFlags = CKeyboardControl::KEYFLAG_REDDOT;
|
||
|
else if(KeyboardMap[ndx] != 0)
|
||
|
newFlags = CKeyboardControl::KEYFLAG_BRIGHTDOT;
|
||
|
if(newFlags != oldFlags || oldSmp != KeyboardMap[ndx])
|
||
|
{
|
||
|
m_Keyboard.SetFlags(iNote, newFlags);
|
||
|
m_Keyboard.SetSample(iNote, KeyboardMap[ndx]);
|
||
|
redraw = true;
|
||
|
}
|
||
|
}
|
||
|
if(redraw)
|
||
|
m_Keyboard.InvalidateRect(NULL, FALSE);
|
||
|
}
|
||
|
|
||
|
|
||
|
LRESULT CSampleMapDlg::OnKeyboardNotify(WPARAM wParam, LPARAM lParam)
|
||
|
{
|
||
|
TCHAR s[32] = _T("--");
|
||
|
|
||
|
if((lParam >= 0) && (lParam < 3 * 12))
|
||
|
{
|
||
|
const SAMPLEINDEX sample = static_cast<SAMPLEINDEX>(m_CbnSample.GetItemData(m_CbnSample.GetCurSel()));
|
||
|
const uint32 baseOctave = m_SbOctave.GetPos() & 7;
|
||
|
|
||
|
const CString temp = mpt::ToCString(sndFile.GetNoteName(static_cast<ModCommand::NOTE>(lParam + 1 + 12 * baseOctave), m_nInstrument));
|
||
|
if(temp.GetLength() >= mpt::saturate_cast<int>(std::size(s)))
|
||
|
wsprintf(s, _T("%s"), _T("..."));
|
||
|
else
|
||
|
wsprintf(s, _T("%s"), temp.GetString());
|
||
|
|
||
|
ModInstrument *pIns = sndFile.Instruments[m_nInstrument];
|
||
|
if((wParam == KBDNOTIFY_LBUTTONDOWN) && (sample < MAX_SAMPLES) && (pIns))
|
||
|
{
|
||
|
const uint32 note = static_cast<uint32>(baseOctave * 12 + lParam);
|
||
|
|
||
|
if(mouseAction == mouseUnknown)
|
||
|
{
|
||
|
// Mouse down -> decide if we are going to set or remove notes
|
||
|
mouseAction = mouseSet;
|
||
|
if(KeyboardMap[note] == sample)
|
||
|
{
|
||
|
mouseAction = (KeyboardMap[note] == pIns->Keyboard[note]) ? mouseZero : mouseUnset;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
switch(mouseAction)
|
||
|
{
|
||
|
case mouseSet:
|
||
|
KeyboardMap[note] = sample;
|
||
|
break;
|
||
|
case mouseUnset:
|
||
|
KeyboardMap[note] = pIns->Keyboard[note];
|
||
|
break;
|
||
|
case mouseZero:
|
||
|
if(KeyboardMap[note] == sample)
|
||
|
{
|
||
|
KeyboardMap[note] = 0;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
OnUpdateKeyboard();
|
||
|
}
|
||
|
}
|
||
|
if(wParam == KBDNOTIFY_LBUTTONUP)
|
||
|
{
|
||
|
mouseAction = mouseUnknown;
|
||
|
}
|
||
|
SetDlgItemText(IDC_TEXT2, s);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
void CSampleMapDlg::OnOK()
|
||
|
{
|
||
|
ModInstrument *pIns = sndFile.Instruments[m_nInstrument];
|
||
|
if(pIns)
|
||
|
{
|
||
|
bool modified = false;
|
||
|
for(UINT i = 0; i < NOTE_MAX; i++)
|
||
|
{
|
||
|
if(KeyboardMap[i] != pIns->Keyboard[i])
|
||
|
{
|
||
|
pIns->Keyboard[i] = KeyboardMap[i];
|
||
|
modified = true;
|
||
|
}
|
||
|
}
|
||
|
if(modified)
|
||
|
{
|
||
|
CDialog::OnOK();
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
CDialog::OnCancel();
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
// Edit history dialog
|
||
|
|
||
|
BEGIN_MESSAGE_MAP(CEditHistoryDlg, ResizableDialog)
|
||
|
ON_COMMAND(IDC_BTN_CLEAR, &CEditHistoryDlg::OnClearHistory)
|
||
|
END_MESSAGE_MAP()
|
||
|
|
||
|
|
||
|
BOOL CEditHistoryDlg::OnInitDialog()
|
||
|
{
|
||
|
ResizableDialog::OnInitDialog();
|
||
|
|
||
|
CString s;
|
||
|
uint64 totalTime = 0;
|
||
|
const auto &editHistory = m_modDoc.GetSoundFile().GetFileHistory();
|
||
|
const bool isEmpty = editHistory.empty();
|
||
|
|
||
|
for(const auto &entry : editHistory)
|
||
|
{
|
||
|
totalTime += entry.openTime;
|
||
|
|
||
|
// Date
|
||
|
CString sDate;
|
||
|
if(entry.HasValidDate())
|
||
|
{
|
||
|
TCHAR szDate[32];
|
||
|
_tcsftime(szDate, std::size(szDate), _T("%d %b %Y, %H:%M:%S"), &entry.loadDate);
|
||
|
sDate = szDate;
|
||
|
} else
|
||
|
{
|
||
|
sDate = _T("<unknown date>");
|
||
|
}
|
||
|
// Time + stuff
|
||
|
uint32 duration = mpt::saturate_round<uint32>(entry.openTime / HISTORY_TIMER_PRECISION);
|
||
|
s += MPT_CFORMAT("Loaded {}, open for {}h {}m {}s\r\n")(
|
||
|
sDate, mpt::cfmt::dec(duration / 3600), mpt::cfmt::dec0<2>((duration / 60) % 60), mpt::cfmt::dec0<2>(duration % 60));
|
||
|
}
|
||
|
if(isEmpty)
|
||
|
{
|
||
|
s = _T("No information available about the previous edit history of this module.");
|
||
|
}
|
||
|
SetDlgItemText(IDC_EDIT_HISTORY, s);
|
||
|
|
||
|
// Total edit time
|
||
|
s.Empty();
|
||
|
if(totalTime)
|
||
|
{
|
||
|
totalTime = mpt::saturate_round<uint64>(totalTime / HISTORY_TIMER_PRECISION);
|
||
|
|
||
|
s.Format(_T("Total edit time: %lluh %02llum %02llus (%zu session%s)"), totalTime / 3600, (totalTime / 60) % 60, totalTime % 60, editHistory.size(), (editHistory.size() != 1) ? _T("s") : _T(""));
|
||
|
SetDlgItemText(IDC_TOTAL_EDIT_TIME, s);
|
||
|
// Window title
|
||
|
s.Format(_T("Edit History for %s"), m_modDoc.GetTitle().GetString());
|
||
|
SetWindowText(s);
|
||
|
}
|
||
|
// Enable or disable Clear button
|
||
|
GetDlgItem(IDC_BTN_CLEAR)->EnableWindow(isEmpty ? FALSE : TRUE);
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
void CEditHistoryDlg::OnClearHistory()
|
||
|
{
|
||
|
if(!m_modDoc.GetSoundFile().GetFileHistory().empty())
|
||
|
{
|
||
|
m_modDoc.GetSoundFile().GetFileHistory().clear();
|
||
|
m_modDoc.SetModified();
|
||
|
OnInitDialog();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////
|
||
|
// Generic input dialog
|
||
|
|
||
|
void CInputDlg::DoDataExchange(CDataExchange* pDX)
|
||
|
{
|
||
|
CDialog::DoDataExchange(pDX);
|
||
|
if(m_minValueInt == m_maxValueInt && m_minValueDbl == m_maxValueDbl)
|
||
|
{
|
||
|
// Only need this for freeform text
|
||
|
DDX_Control(pDX, IDC_EDIT1, m_edit);
|
||
|
}
|
||
|
DDX_Control(pDX, IDC_SPIN1, m_spin);
|
||
|
}
|
||
|
|
||
|
|
||
|
BOOL CInputDlg::OnInitDialog()
|
||
|
{
|
||
|
CDialog::OnInitDialog();
|
||
|
SetDlgItemText(IDC_PROMPT, m_description);
|
||
|
|
||
|
// Get all current control sizes and positions
|
||
|
CRect windowRect, labelRect, inputRect, okRect, cancelRect;
|
||
|
GetWindowRect(windowRect);
|
||
|
GetDlgItem(IDC_PROMPT)->GetWindowRect(labelRect);
|
||
|
GetDlgItem(IDC_EDIT1)->GetWindowRect(inputRect);
|
||
|
GetDlgItem(IDOK)->GetWindowRect(okRect);
|
||
|
GetDlgItem(IDCANCEL)->GetWindowRect(cancelRect);
|
||
|
ScreenToClient(labelRect);
|
||
|
ScreenToClient(inputRect);
|
||
|
ScreenToClient(okRect);
|
||
|
ScreenToClient(cancelRect);
|
||
|
|
||
|
// Find out how big our label shall be
|
||
|
HDC dc = ::GetDC(m_hWnd);
|
||
|
CRect textRect(0,0,0,0);
|
||
|
DrawText(dc, m_description, m_description.GetLength(), textRect, DT_CALCRECT);
|
||
|
LPtoDP(dc, &textRect.BottomRight(), 1);
|
||
|
::ReleaseDC(m_hWnd, dc);
|
||
|
if(textRect.right < 320) textRect.right = 320;
|
||
|
const int windowWidth = windowRect.Width() - labelRect.Width() + textRect.right;
|
||
|
const int windowHeight = windowRect.Height() - labelRect.Height() + textRect.bottom;
|
||
|
|
||
|
// Resize and move all controls
|
||
|
GetDlgItem(IDC_PROMPT)->SetWindowPos(nullptr, 0, 0, textRect.right, textRect.bottom, SWP_NOMOVE | SWP_NOZORDER);
|
||
|
GetDlgItem(IDC_EDIT1)->SetWindowPos(nullptr, inputRect.left, labelRect.top + textRect.bottom + (inputRect.top - labelRect.bottom), textRect.right, inputRect.Height(), SWP_NOZORDER);
|
||
|
GetDlgItem(IDOK)->SetWindowPos(nullptr, windowWidth - (windowRect.Width() - okRect.left), windowHeight - (windowRect.Height() - okRect.top), 0, 0, SWP_NOSIZE | SWP_NOZORDER);
|
||
|
GetDlgItem(IDCANCEL)->SetWindowPos(nullptr, windowWidth - (windowRect.Width() - cancelRect.left), windowHeight - (windowRect.Height() - cancelRect.top), 0, 0, SWP_NOSIZE | SWP_NOZORDER);
|
||
|
SetWindowPos(nullptr, 0, 0, windowWidth, windowHeight, SWP_NOMOVE | SWP_NOZORDER);
|
||
|
|
||
|
if(m_minValueInt != m_maxValueInt)
|
||
|
{
|
||
|
// Numeric (int)
|
||
|
m_spin.SetRange32(m_minValueInt, m_maxValueInt);
|
||
|
m_edit.SubclassDlgItem(IDC_EDIT1, this);
|
||
|
m_edit.ModifyStyle(0, ES_NUMBER);
|
||
|
m_edit.AllowNegative(m_minValueInt < 0);
|
||
|
m_edit.AllowFractions(false);
|
||
|
SetDlgItemInt(IDC_EDIT1, resultAsInt);
|
||
|
m_spin.SetBuddy(&m_edit);
|
||
|
} else if(m_minValueDbl != m_maxValueDbl)
|
||
|
{
|
||
|
// Numeric (double)
|
||
|
m_spin.SetRange32(static_cast<int32>(m_minValueDbl), static_cast<int32>(m_maxValueDbl));
|
||
|
m_edit.SubclassDlgItem(IDC_EDIT1, this);
|
||
|
m_edit.ModifyStyle(0, ES_NUMBER);
|
||
|
m_edit.AllowNegative(m_minValueDbl < 0);
|
||
|
m_edit.AllowFractions(true);
|
||
|
m_edit.SetDecimalValue(resultAsDouble);
|
||
|
m_spin.SetBuddy(&m_edit);
|
||
|
} else
|
||
|
{
|
||
|
// Text
|
||
|
m_spin.ShowWindow(SW_HIDE);
|
||
|
if(m_maxLength > 0)
|
||
|
Edit_LimitText(m_edit, m_maxLength);
|
||
|
SetDlgItemText(IDC_EDIT1, resultAsString);
|
||
|
}
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
|
||
|
void CInputDlg::OnOK()
|
||
|
{
|
||
|
CDialog::OnOK();
|
||
|
GetDlgItemText(IDC_EDIT1, resultAsString);
|
||
|
resultAsInt = static_cast<int32>(GetDlgItemInt(IDC_EDIT1));
|
||
|
Limit(resultAsInt, m_minValueInt, m_maxValueInt);
|
||
|
m_edit.GetDecimalValue(resultAsDouble);
|
||
|
Limit(resultAsDouble, m_minValueDbl, m_maxValueDbl);
|
||
|
}
|
||
|
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////////////////
|
||
|
// Messagebox with 'don't show again'-option.
|
||
|
|
||
|
class CMsgBoxHidable : public CDialog
|
||
|
{
|
||
|
public:
|
||
|
CMsgBoxHidable(const TCHAR *strMsg, bool checkStatus = true, CWnd* pParent = NULL);
|
||
|
enum { IDD = IDD_MSGBOX_HIDABLE };
|
||
|
|
||
|
const TCHAR *m_StrMsg;
|
||
|
int m_nCheckStatus;
|
||
|
protected:
|
||
|
void DoDataExchange(CDataExchange* pDX) override; // DDX/DDV support
|
||
|
BOOL OnInitDialog() override;
|
||
|
};
|
||
|
|
||
|
|
||
|
struct MsgBoxHidableMessage
|
||
|
{
|
||
|
const TCHAR *message;
|
||
|
uint32 mask;
|
||
|
bool defaultDontShowAgainStatus; // true for don't show again, false for show again.
|
||
|
};
|
||
|
|
||
|
static constexpr MsgBoxHidableMessage HidableMessages[] =
|
||
|
{
|
||
|
{ _T("Note: First two bytes of oneshot samples are silenced for ProTracker compatibility."), 1, true },
|
||
|
{ _T("Hint: To create IT-files without MPT-specific extensions included, try compatibility export from File-menu."), 1 << 1, true },
|
||
|
{ _T("Press OK to apply signed/unsigned conversion\n (note: this often significantly increases volume level)"), 1 << 2, false },
|
||
|
{ _T("Hint: To create XM-files without MPT-specific extensions included, try compatibility export from File-menu."), 1 << 3, true },
|
||
|
{ _T("Warning: The exported file will not contain any of MPT's file format hacks."), 1 << 4, true },
|
||
|
};
|
||
|
|
||
|
static_assert(mpt::array_size<decltype(HidableMessages)>::size == enMsgBoxHidableMessage_count);
|
||
|
|
||
|
// Messagebox with 'don't show this again'-checkbox. Uses parameter 'enMsg'
|
||
|
// to get the needed information from message array, and updates the variable that
|
||
|
// controls the show/don't show-flags.
|
||
|
void MsgBoxHidable(enMsgBoxHidableMessage enMsg)
|
||
|
{
|
||
|
// Check whether the message should be shown.
|
||
|
if((TrackerSettings::Instance().gnMsgBoxVisiblityFlags & HidableMessages[enMsg].mask) == 0)
|
||
|
return;
|
||
|
|
||
|
// Show dialog.
|
||
|
CMsgBoxHidable dlg(HidableMessages[enMsg].message, HidableMessages[enMsg].defaultDontShowAgainStatus);
|
||
|
dlg.DoModal();
|
||
|
|
||
|
// Update visibility flags.
|
||
|
const uint32 mask = HidableMessages[enMsg].mask;
|
||
|
if(dlg.m_nCheckStatus == BST_CHECKED)
|
||
|
TrackerSettings::Instance().gnMsgBoxVisiblityFlags &= ~mask;
|
||
|
else
|
||
|
TrackerSettings::Instance().gnMsgBoxVisiblityFlags |= mask;
|
||
|
}
|
||
|
|
||
|
|
||
|
CMsgBoxHidable::CMsgBoxHidable(const TCHAR *strMsg, bool checkStatus, CWnd* pParent)
|
||
|
: CDialog(CMsgBoxHidable::IDD, pParent)
|
||
|
, m_StrMsg(strMsg)
|
||
|
, m_nCheckStatus((checkStatus) ? BST_CHECKED : BST_UNCHECKED)
|
||
|
{}
|
||
|
|
||
|
BOOL CMsgBoxHidable::OnInitDialog()
|
||
|
{
|
||
|
CDialog::OnInitDialog();
|
||
|
SetDlgItemText(IDC_MESSAGETEXT, m_StrMsg);
|
||
|
SetWindowText(AfxGetAppName());
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
void CMsgBoxHidable::DoDataExchange(CDataExchange* pDX)
|
||
|
{
|
||
|
CDialog::DoDataExchange(pDX);
|
||
|
DDX_Check(pDX, IDC_DONTSHOWAGAIN, m_nCheckStatus);
|
||
|
}
|
||
|
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
|
||
|
void AppendNotesToControl(CComboBox& combobox, ModCommand::NOTE noteStart, ModCommand::NOTE noteEnd)
|
||
|
{
|
||
|
const ModCommand::NOTE upperLimit = std::min(ModCommand::NOTE(NOTE_MAX), noteEnd);
|
||
|
for(ModCommand::NOTE note = noteStart; note <= upperLimit; note++)
|
||
|
combobox.SetItemData(combobox.AddString(mpt::ToCString(CSoundFile::GetNoteName(note, CSoundFile::GetDefaultNoteNames()))), note);
|
||
|
}
|
||
|
|
||
|
|
||
|
void AppendNotesToControlEx(CComboBox& combobox, const CSoundFile &sndFile, INSTRUMENTINDEX nInstr, ModCommand::NOTE noteStart, ModCommand::NOTE noteEnd)
|
||
|
{
|
||
|
bool addSpecial = noteStart == noteEnd;
|
||
|
if(noteStart == noteEnd)
|
||
|
{
|
||
|
noteStart = sndFile.GetModSpecifications().noteMin;
|
||
|
noteEnd = sndFile.GetModSpecifications().noteMax;
|
||
|
}
|
||
|
for(ModCommand::NOTE note = noteStart; note <= noteEnd; note++)
|
||
|
{
|
||
|
combobox.SetItemData(combobox.AddString(mpt::ToCString(sndFile.GetNoteName(note, nInstr))), note);
|
||
|
}
|
||
|
if(addSpecial)
|
||
|
{
|
||
|
for(ModCommand::NOTE note = NOTE_MIN_SPECIAL - 1; note++ < NOTE_MAX_SPECIAL;)
|
||
|
{
|
||
|
if(sndFile.GetModSpecifications().HasNote(note))
|
||
|
combobox.SetItemData(combobox.AddString(szSpecialNoteNamesMPT[note - NOTE_MIN_SPECIAL]), note);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
OPENMPT_NAMESPACE_END
|