/*
 * TrackerSettings.cpp
 * -------------------
 * Purpose: Code for managing, loading and saving all applcation settings.
 * 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 "Moddoc.h"
#include "Mainfrm.h"
#include "mpt/environment/environment.hpp"
#include "mpt/uuid/uuid.hpp"
#include "openmpt/sounddevice/SoundDevice.hpp"
#include "openmpt/sounddevice/SoundDeviceManager.hpp"
#include "../common/version.h"
#include "UpdateCheck.h"
#include "Mpdlgs.h"
#include "../common/mptStringBuffer.h"
#include "TrackerSettings.h"
#include "../common/misc_util.h"
#include "PatternClipboard.h"
#include "../common/ComponentManager.h"
#include "ExceptionHandler.h"
#include "../soundlib/mod_specifications.h"
#include "../soundlib/Tables.h"
#include "../common/mptFileIO.h"
#include "../soundlib/tuningcollection.h"
#include "TuningDialog.h"


#include <algorithm>


OPENMPT_NAMESPACE_BEGIN


#define OLD_SOUNDSETUP_REVERSESTEREO         0x20
#define OLD_SOUNDSETUP_SECONDARY             0x40
#define OLD_SOUNDSETUP_NOBOOSTTHREADPRIORITY 0x80

#ifndef NO_EQ

constexpr EQPreset FlatEQPreset = {"Flat", {16, 16, 16, 16, 16, 16}, {125, 300, 600, 1250, 4000, 8000}};

#endif // !NO_EQ


TrackerSettings &TrackerSettings::Instance()
{
	return theApp.GetTrackerSettings();
}


static Version GetPreviousSettingsVersion(const mpt::ustring &iniVersion)
{
	if(!iniVersion.empty())
	{
		return Version::Parse(iniVersion);
	} else
	{
		// No version stored.
		// This is the first run, thus set the previous version to our current
		// version which will avoid running all settings upgrade code.
		return Version::Current();
	}
}


mpt::ustring SettingsModTypeToString(MODTYPE modtype)
{
	return mpt::ToUnicode(mpt::Charset::UTF8, CSoundFile::GetModSpecifications(modtype).fileExtension);
}

MODTYPE SettingsStringToModType(const mpt::ustring &str)
{
	return CModSpecifications::ExtensionToType(mpt::ToCharset(mpt::Charset::UTF8, str));
}


static uint32 GetDefaultPatternSetup()
{
	return PATTERN_PLAYNEWNOTE | PATTERN_EFFECTHILIGHT
		| PATTERN_CENTERROW | PATTERN_DRAGNDROPEDIT
		| PATTERN_FLATBUTTONS | PATTERN_NOEXTRALOUD | PATTERN_2NDHIGHLIGHT
		| PATTERN_STDHIGHLIGHT | PATTERN_SHOWPREVIOUS | PATTERN_CONTSCROLL
		| PATTERN_SYNCMUTE | PATTERN_AUTODELAY | PATTERN_NOTEFADE
		| PATTERN_SHOWDEFAULTVOLUME | PATTERN_LIVEUPDATETREE | PATTERN_SYNCSAMPLEPOS;
}


void SampleUndoBufferSize::CalculateSize()
{
	if(sizePercent < 0)
		sizePercent = 0;
	MEMORYSTATUSEX memStatus;
	memStatus.dwLength = sizeof(MEMORYSTATUSEX);
	GlobalMemoryStatusEx(&memStatus);
	// The setting is a percentage of the memory that's actually *available* to OpenMPT, which is a max of 4GB in 32-bit mode.
	sizeByte = mpt::saturate_cast<size_t>(std::min(memStatus.ullTotalPhys, DWORDLONG(SIZE_T_MAX)) * sizePercent / 100);

	// Pretend there's at least one MiB of memory (haha)
	if(sizePercent != 0 && sizeByte < 1 * 1024 * 1024)
	{
		sizeByte = 1 * 1024 * 1024;
	}
}


DebugSettings::DebugSettings(SettingsContainer &conf)
	: conf(conf)
	// Debug
#if !defined(MPT_LOG_IS_DISABLED)
	, DebugLogLevel(conf, U_("Debug"), U_("LogLevel"), static_cast<int>(mpt::log::GlobalLogLevel))
	, DebugLogFacilitySolo(conf, U_("Debug"), U_("LogFacilitySolo"), std::string())
	, DebugLogFacilityBlocked(conf, U_("Debug"), U_("LogFacilityBlocked"), std::string())
	, DebugLogFileEnable(conf, U_("Debug"), U_("LogFileEnable"), mpt::log::FileEnabled)
	, DebugLogDebuggerEnable(conf, U_("Debug"), U_("LogDebuggerEnable"), mpt::log::DebuggerEnabled)
	, DebugLogConsoleEnable(conf, U_("Debug"), U_("LogConsoleEnable"), mpt::log::ConsoleEnabled)
#endif
	, DebugTraceEnable(conf, U_("Debug"), U_("TraceEnable"), false)
	, DebugTraceSize(conf, U_("Debug"), U_("TraceSize"), 1000000)
	, DebugTraceAlwaysDump(conf, U_("Debug"), U_("TraceAlwaysDump"), false)
	, DebugStopSoundDeviceOnCrash(conf, U_("Debug"), U_("StopSoundDeviceOnCrash"), true)
	, DebugStopSoundDeviceBeforeDump(conf, U_("Debug"), U_("StopSoundDeviceBeforeDump"), false)
	, DebugDelegateToWindowsHandler(conf, U_("Debug"), U_("DelegateToWindowsHandler"), false)
{

	// Duplicate state for debug stuff in order to avoid calling into settings framework from crash context.
	ExceptionHandler::stopSoundDeviceOnCrash = DebugStopSoundDeviceOnCrash;
	ExceptionHandler::stopSoundDeviceBeforeDump = DebugStopSoundDeviceBeforeDump;
	ExceptionHandler::delegateToWindowsHandler = DebugDelegateToWindowsHandler;

		// enable debug features (as early as possible after reading the settings)
	#if !defined(MPT_LOG_IS_DISABLED)
		#if !defined(MPT_LOG_GLOBAL_LEVEL_STATIC)
			mpt::log::GlobalLogLevel = DebugLogLevel;
		#endif
		mpt::log::SetFacilities(DebugLogFacilitySolo, DebugLogFacilityBlocked);
		mpt::log::FileEnabled = DebugLogFileEnable;
		mpt::log::DebuggerEnabled = DebugLogDebuggerEnable;
		mpt::log::ConsoleEnabled = DebugLogConsoleEnable;
	#endif
	if(DebugTraceEnable)
	{
		mpt::log::Trace::Enable(DebugTraceSize);
	}
}


DebugSettings::~DebugSettings()
{
	if(DebugTraceAlwaysDump)
	{
		DebugTraceDump();
	}
}


TrackerSettings::TrackerSettings(SettingsContainer &conf)
	: conf(conf)
	// Version
	, IniVersion(conf, U_("Version"), U_("Version"), mpt::ustring())
	, FirstRun(IniVersion.Get() == mpt::ustring())
	, PreviousSettingsVersion(GetPreviousSettingsVersion(IniVersion))
	, VersionInstallGUID(conf, U_("Version"), U_("InstallGUID"), mpt::UUID())
	// Display
	, m_ShowSplashScreen(conf, U_("Display"), U_("ShowSplashScreen"), true)
	, gbMdiMaximize(conf, U_("Display"), U_("MDIMaximize"), true)
	, highResUI(conf, U_("Display"), U_("HighResUI"), false)
	, glTreeSplitRatio(conf, U_("Display"), U_("MDITreeRatio"), 128)
	, glTreeWindowWidth(conf, U_("Display"), U_("MDITreeWidth"), 160)
	, glGeneralWindowHeight(conf, U_("Display"), U_("MDIGeneralHeight"), 222)
	, glPatternWindowHeight(conf, U_("Display"), U_("MDIPatternHeight"), 152)
	, glSampleWindowHeight(conf, U_("Display"), U_("MDISampleHeight"), 190)
	, glInstrumentWindowHeight(conf, U_("Display"), U_("MDIInstrumentHeight"), 300)
	, glCommentsWindowHeight(conf, U_("Display"), U_("MDICommentsHeight"), 288)
	, glGraphWindowHeight(conf, U_("Display"), U_("MDIGraphHeight"), 288)
	, gnPlugWindowX(conf, U_("Display"), U_("PlugSelectWindowX"), 243)
	, gnPlugWindowY(conf, U_("Display"), U_("PlugSelectWindowY"), 273)
	, gnPlugWindowWidth(conf, U_("Display"), U_("PlugSelectWindowWidth"), 450)
	, gnPlugWindowHeight(conf, U_("Display"), U_("PlugSelectWindowHeight"), 540)
	, gnPlugWindowLast(conf, U_("Display"), U_("PlugSelectWindowLast"), 0)
	, gnMsgBoxVisiblityFlags(conf, U_("Display"), U_("MsgBoxVisibilityFlags"), uint32_max)
	, GUIUpdateInterval(conf, U_("Display"), U_("GUIUpdateInterval"), 0)
	, FSUpdateInterval(conf, U_("Display"), U_("FSUpdateInterval"), 500)
	, VuMeterUpdateInterval(conf, U_("Display"), U_("VuMeterUpdateInterval"), 15)
	, VuMeterDecaySpeedDecibelPerSecond(conf, U_("Display"), U_("VuMeterDecaySpeedDecibelPerSecond"), 88.0f)
	, accidentalFlats(conf, U_("Display"), U_("AccidentalFlats"), false)
	, rememberSongWindows(conf, U_("Display"), U_("RememberSongWindows"), true)
	, showDirsInSampleBrowser(conf, U_("Display"), U_("ShowDirsInSampleBrowser"), false)
	, commentsFont(conf, U_("Display"), U_("Comments Font"), FontSetting(U_("Courier New"), 120))
	, defaultRainbowChannelColors(conf, U_("Display"), U_("DefaultChannelColors"), DefaultChannelColors::Random)
	// Misc
	, defaultModType(conf, U_("Misc"), U_("DefaultModType"), MOD_TYPE_IT)
	, defaultNewFileAction(conf, U_("Misc"), U_("DefaultNewFileAction"), nfDefaultFormat)
	, DefaultPlugVolumeHandling(conf, U_("Misc"), U_("DefaultPlugVolumeHandling"), PLUGIN_VOLUMEHANDLING_IGNORE)
	, autoApplySmoothFT2Ramping(conf, U_("Misc"), U_("SmoothFT2Ramping"), false)
	, MiscITCompressionStereo(conf, U_("Misc"), U_("ITCompressionStereo"), 4)
	, MiscITCompressionMono(conf, U_("Misc"), U_("ITCompressionMono"), 4)
	, MiscSaveChannelMuteStatus(conf, U_("Misc"), U_("SaveChannelMuteStatus"), true)
	, MiscAllowMultipleCommandsPerKey(conf, U_("Misc"), U_("AllowMultipleCommandsPerKey"), false)
	, MiscDistinguishModifiers(conf, U_("Misc"), U_("DistinguishModifiers"), false)
	, MiscProcessPriorityClass(conf, U_("Misc"), U_("ProcessPriorityClass"), ProcessPriorityClassNORMAL)
	, MiscFlushFileBuffersOnSave(conf, U_("Misc"), U_("FlushFileBuffersOnSave"), true)
	, MiscCacheCompleteFileBeforeLoading(conf, U_("Misc"), U_("CacheCompleteFileBeforeLoading"), false)
	, MiscUseSingleInstance(conf, U_("Misc"), U_("UseSingleInstance"), false)
	// Sound Settings
	, m_SoundShowRecordingSettings(false)
	, m_SoundShowDeprecatedDevices(conf, U_("Sound Settings"), U_("ShowDeprecatedDevices"), false)
	, m_SoundDeprecatedDeviceWarningShown(conf, U_("Sound Settings"), U_("DeprecatedDeviceWarningShown"), false)
	, m_SoundSampleRates(conf, U_("Sound Settings"), U_("SampleRates"), GetDefaultSampleRates())
	, m_SoundSettingsOpenDeviceAtStartup(conf, U_("Sound Settings"), U_("OpenDeviceAtStartup"), false)
	, m_SoundSettingsStopMode(conf, U_("Sound Settings"), U_("StopMode"), SoundDeviceStopModeClosed)
	, m_SoundDeviceSettingsUseOldDefaults(false)
	, m_SoundDeviceID_DEPRECATED(SoundDevice::Legacy::ID())
	, m_SoundDeviceIdentifier(conf, U_("Sound Settings"), U_("Device"), SoundDevice::Identifier())
	, MixerMaxChannels(conf, U_("Sound Settings"), U_("MixChannels"), MixerSettings().m_nMaxMixChannels)
	, MixerDSPMask(conf, U_("Sound Settings"), U_("Quality"), MixerSettings().DSPMask)
	, MixerFlags(conf, U_("Sound Settings"), U_("SoundSetup"), MixerSettings().MixerFlags)
	, MixerSamplerate(conf, U_("Sound Settings"), U_("Mixing_Rate"), MixerSettings().gdwMixingFreq)
	, MixerOutputChannels(conf, U_("Sound Settings"), U_("ChannelMode"), MixerSettings().gnChannels)
	, MixerPreAmp(conf, U_("Sound Settings"), U_("PreAmp"), MixerSettings().m_nPreAmp)
	, MixerStereoSeparation(conf, U_("Sound Settings"), U_("StereoSeparation"), MixerSettings().m_nStereoSeparation)
	, MixerVolumeRampUpMicroseconds(conf, U_("Sound Settings"), U_("VolumeRampUpMicroseconds"), MixerSettings().GetVolumeRampUpMicroseconds())
	, MixerVolumeRampDownMicroseconds(conf, U_("Sound Settings"), U_("VolumeRampDownMicroseconds"), MixerSettings().GetVolumeRampDownMicroseconds())
	, MixerNumInputChannels(conf, U_("Sound Settings"), U_("NumInputChannels"), static_cast<uint32>(MixerSettings().NumInputChannels))
	, ResamplerMode(conf, U_("Sound Settings"), U_("SrcMode"), CResamplerSettings().SrcMode)
	, ResamplerSubMode(conf, U_("Sound Settings"), U_("XMMSModplugResamplerWFIRType"), CResamplerSettings().gbWFIRType)
	, ResamplerCutoffPercent(conf, U_("Sound Settings"), U_("ResamplerWFIRCutoff"), mpt::saturate_round<int32>(CResamplerSettings().gdWFIRCutoff * 100.0))
	, ResamplerEmulateAmiga(conf, U_("Sound Settings"), U_("ResamplerEmulateAmiga"), Resampling::AmigaFilter::A1200)
	, SoundBoostedThreadPriority(conf, U_("Sound Settings"), U_("BoostedThreadPriority"), SoundDevice::AppInfo().BoostedThreadPriorityXP)
	, SoundBoostedThreadMMCSSClass(conf, U_("Sound Settings"), U_("BoostedThreadMMCSSClass"), SoundDevice::AppInfo().BoostedThreadMMCSSClassVista)
	, SoundBoostedThreadRealtimePosix(conf, U_("Sound Settings"), U_("BoostedThreadRealtimeLinux"), SoundDevice::AppInfo().BoostedThreadRealtimePosix)
	, SoundBoostedThreadNicenessPosix(conf, U_("Sound Settings"), U_("BoostedThreadNicenessPosix"), SoundDevice::AppInfo().BoostedThreadNicenessPosix)
	, SoundBoostedThreadRtprioPosix(conf, U_("Sound Settings"), U_("BoostedThreadRtprioLinux"), SoundDevice::AppInfo().BoostedThreadRtprioPosix)
	, SoundMaskDriverCrashes(conf, U_("Sound Settings"), U_("MaskDriverCrashes"), SoundDevice::AppInfo().MaskDriverCrashes)
	, SoundAllowDeferredProcessing(conf, U_("Sound Settings"), U_("AllowDeferredProcessing"), SoundDevice::AppInfo().AllowDeferredProcessing)
	// MIDI Settings
	, m_nMidiDevice(conf, U_("MIDI Settings"), U_("MidiDevice"), 0)
	, midiDeviceName(conf, U_("MIDI Settings"), U_("MidiDeviceName"), _T(""))
	, m_dwMidiSetup(conf, U_("MIDI Settings"), U_("MidiSetup"), MIDISETUP_RECORDVELOCITY | MIDISETUP_RECORDNOTEOFF | MIDISETUP_TRANSPOSEKEYBOARD | MIDISETUP_MIDITOPLUG)
	, aftertouchBehaviour(conf, U_("MIDI Settings"), U_("AftertouchBehaviour"), atDoNotRecord)
	, midiVelocityAmp(conf, U_("MIDI Settings"), U_("MidiVelocityAmp"), 100)
	, midiIgnoreCCs(conf, U_("MIDI Settings"), U_("IgnoredCCs"), std::bitset<128>())
	, midiImportPatternLen(conf, U_("MIDI Settings"), U_("MidiImportPatLen"), 128)
	, midiImportQuantize(conf, U_("MIDI Settings"), U_("MidiImportQuantize"), 32)
	, midiImportTicks(conf, U_("MIDI Settings"), U_("MidiImportTicks"), 6)
	// Pattern Editor
	, gbLoopSong(conf, U_("Pattern Editor"), U_("LoopSong"), true)
	, gnPatternSpacing(conf, U_("Pattern Editor"), U_("Spacing"), 0)
	, gbPatternVUMeters(conf, U_("Pattern Editor"), U_("VU-Meters"), true)
	, gbPatternPluginNames(conf, U_("Pattern Editor"), U_("Plugin-Names"), true)
	, gbPatternRecord(conf, U_("Pattern Editor"), U_("Record"), true)
	, patternNoEditPopup(conf, U_("Pattern Editor"), U_("NoEditPopup"), false)
	, patternStepCommands(conf, U_("Pattern Editor"), U_("EditStepAppliesToCommands"), false)
	, m_dwPatternSetup(conf, U_("Pattern Editor"), U_("PatternSetup"), GetDefaultPatternSetup())
	, m_nRowHighlightMeasures(conf, U_("Pattern Editor"), U_("RowSpacing"), 16)
	, m_nRowHighlightBeats(conf, U_("Pattern Editor"), U_("RowSpacing2"), 4)
	, recordQuantizeRows(conf, U_("Pattern Editor"), U_("RecordQuantize"), 0)
	, gnAutoChordWaitTime(conf, U_("Pattern Editor"), U_("AutoChordWaitTime"), 60)
	, orderlistMargins(conf, U_("Pattern Editor"), U_("DefaultSequenceMargins"), 0)
	, rowDisplayOffset(conf, U_("Pattern Editor"), U_("RowDisplayOffset"), 0)
	, patternFont(conf, U_("Pattern Editor"), U_("Font"), FontSetting(PATTERNFONT_SMALL, 0))
	, patternFontDot(conf, U_("Pattern Editor"), U_("FontDot"), U_("."))
	, effectVisWidth(conf, U_("Pattern Editor"), U_("EffectVisWidth"), -1)
	, effectVisHeight(conf, U_("Pattern Editor"), U_("EffectVisHeight"), -1)
	, effectVisX(conf, U_("Pattern Editor"), U_("EffectVisX"), int32_min)
	, effectVisY(conf, U_("Pattern Editor"), U_("EffectVisY"), int32_min)
	, patternAccessibilityFormat(conf, U_("Pattern Editor"), U_("AccessibilityFormat"), _T("Row %row%, Channel %channel%, %column_type%: %column_description%"))
	, patternAlwaysDrawWholePatternOnScrollSlow(conf, U_("Pattern Editor"), U_("AlwaysDrawWholePatternOnScrollSlow"), false)
	, orderListOldDropBehaviour(conf, U_("Pattern Editor"), U_("OrderListOldDropBehaviour"), false)
	// Sample Editor
	, m_SampleUndoBufferSize(conf, U_("Sample Editor"), U_("UndoBufferSize"), SampleUndoBufferSize())
	, sampleEditorKeyBehaviour(conf, U_("Sample Editor"), U_("KeyBehaviour"), seNoteOffOnNewKey)
	, m_defaultSampleFormat(conf, U_("Sample Editor"), U_("DefaultFormat"), dfFLAC)
	, sampleEditorTimelineFormat(conf, U_("Sample Editor"), U_("TimelineFormat"), TimelineFormat::Seconds)
	, sampleEditorDefaultResampler(conf, U_("Sample Editor"), U_("DefaultResampler"), SRCMODE_DEFAULT)
	, m_nFinetuneStep(conf, U_("Sample Editor"), U_("FinetuneStep"), 10)
	, m_FLACCompressionLevel(conf, U_("Sample Editor"), U_("FLACCompressionLevel"), 5)
	, compressITI(conf, U_("Sample Editor"), U_("CompressITI"), true)
	, m_MayNormalizeSamplesOnLoad(conf, U_("Sample Editor"), U_("MayNormalizeSamplesOnLoad"), true)
	, previewInFileDialogs(conf, U_("Sample Editor"), U_("PreviewInFileDialogs"), false)
	, cursorPositionInHex(conf, U_("Sample Editor"), U_("CursorPositionInHex"), false)
	// Export
	, ExportDefaultToSoundcardSamplerate(conf, U_("Export"), U_("DefaultToSoundcardSamplerate"), true)
	, ExportStreamEncoderSettings(conf, U_("Export"))
	// Components
	, ComponentsLoadOnStartup(conf, U_("Components"), U_("LoadOnStartup"), ComponentManagerSettingsDefault().LoadOnStartup())
	, ComponentsKeepLoaded(conf, U_("Components"), U_("KeepLoaded"), ComponentManagerSettingsDefault().KeepLoaded())
	// AutoSave
	, CreateBackupFiles(conf, U_("AutoSave"), U_("CreateBackupFiles"), true)
	, AutosaveEnabled(conf, U_("AutoSave"), U_("Enabled"), true)
	, AutosaveIntervalMinutes(conf, U_("AutoSave"), U_("IntervalMinutes"), 10)
	, AutosaveHistoryDepth(conf, U_("AutoSave"), U_("BackupHistory"), 3)
	, AutosaveUseOriginalPath(conf, U_("AutoSave"), U_("UseOriginalPath"), true)
	, AutosavePath(conf, U_("AutoSave"), U_("Path"), mpt::GetTempDirectory())
	// Paths
	, PathSongs(conf, U_("Paths"), U_("Songs_Directory"), mpt::PathString())
	, PathSamples(conf, U_("Paths"), U_("Samples_Directory"), mpt::PathString())
	, PathInstruments(conf, U_("Paths"), U_("Instruments_Directory"), mpt::PathString())
	, PathPlugins(conf, U_("Paths"), U_("Plugins_Directory"), mpt::PathString())
	, PathPluginPresets(conf, U_("Paths"), U_("Plugin_Presets_Directory"), mpt::PathString())
	, PathExport(conf, U_("Paths"), U_("Export_Directory"), mpt::PathString())
	, PathTunings(theApp.GetConfigPath() + P_("tunings\\"))
	, PathUserTemplates(theApp.GetConfigPath() + P_("TemplateModules\\"))
	// Default template
	, defaultTemplateFile(conf, U_("Paths"), U_("DefaultTemplate"), mpt::PathString())
	, defaultArtist(conf, U_("Misc"), U_("DefaultArtist"), mpt::getenv(U_("USERNAME")).value_or(U_("")))
	// MRU List
	, mruListLength(conf, U_("Misc"), U_("MRUListLength"), 10)
	// Plugins
	, bridgeAllPlugins(conf, U_("VST Plugins"), U_("BridgeAllPlugins"), false)
	, enableAutoSuspend(conf, U_("VST Plugins"), U_("EnableAutoSuspend"), false)
	, midiMappingInPluginEditor(conf, U_("VST Plugins"), U_("EnableMidiMappingInEditor"), true)
	, pluginProjectPath(conf, U_("VST Plugins"), U_("ProjectPath"), mpt::ustring())
	, vstHostProductString(conf, U_("VST Plugins"), U_("HostProductString"), "OpenMPT")
	, vstHostVendorString(conf, U_("VST Plugins"), U_("HostVendorString"), "OpenMPT project")
	, vstHostVendorVersion(conf, U_("VST Plugins"), U_("HostVendorVersion"), Version::Current().GetRawVersion())
	// Broken Plugins Workarounds
	, BrokenPluginsWorkaroundVSTMaskAllCrashes(conf, U_("Broken Plugins Workarounds"), U_("VSTMaskAllCrashes"), true)  // TODO: really should be false
	, BrokenPluginsWorkaroundVSTNeverUnloadAnyPlugin(conf, U_("Broken Plugins Workarounds"), U_("VSTNeverUnloadAnyPlugin"), false)
#if defined(MPT_ENABLE_UPDATE)
	// Update
	, UpdateEnabled(conf, U_("Update"), U_("Enabled"), true)
	, UpdateInstallAutomatically(conf, U_("Update"), U_("InstallAutomatically"), false)
	, UpdateLastUpdateCheck(conf, U_("Update"), U_("LastUpdateCheck"), mpt::Date::Unix(time_t()))
	, UpdateUpdateCheckPeriod_DEPRECATED(conf, U_("Update"), U_("UpdateCheckPeriod"), 7)
	, UpdateIntervalDays(conf, U_("Update"), U_("UpdateCheckIntervalDays"), 7)
	, UpdateChannel(conf, U_("Update"), U_("Channel"), UpdateChannelRelease)
	, UpdateUpdateURL_DEPRECATED(conf, U_("Update"), U_("UpdateURL"), U_("https://update.openmpt.org/check/$VERSION/$GUID"))
	, UpdateAPIURL(conf, U_("Update"), U_("APIURL"), CUpdateCheck::GetDefaultAPIURL())
	, UpdateStatisticsConsentAsked(conf, U_("Update"), U_("StatistisConsentAsked"), false)
	, UpdateStatistics(conf, U_("Update"), U_("Statistis"), false)
	, UpdateSendGUID_DEPRECATED(conf, U_("Update"), U_("SendGUID"), false)
	, UpdateShowUpdateHint(conf, U_("Update"), U_("ShowUpdateHint"), true)
	, UpdateIgnoreVersion(conf, U_("Update"), U_("IgnoreVersion"), _T(""))
	, UpdateSkipSignatureVerificationUNSECURE(conf, U_("Update"), U_("SkipSignatureVerification"), false)
	, UpdateSigningKeysRootAnchors(conf, U_("Update"), U_("SigningKeysRootAnchors"), CUpdateCheck::GetDefaultUpdateSigningKeysRootAnchors())
#endif // MPT_ENABLE_UPDATE
	// Wine suppport
	, WineSupportEnabled(conf, U_("WineSupport"), U_("Enabled"), false)
	, WineSupportAlwaysRecompile(conf, U_("WineSupport"), U_("AlwaysRecompile"), false)
	, WineSupportAskCompile(conf, U_("WineSupport"), U_("AskCompile"), false)
	, WineSupportCompileVerbosity(conf, U_("WineSupport"), U_("CompileVerbosity"), 2) // 0=silent 1=silentmake 2=progresswindow 3=standard 4=verbosemake 5=veryverbosemake 6=msgboxes
	, WineSupportForeignOpenMPT(conf, U_("WineSupport"), U_("ForeignOpenMPT"), false)
	, WineSupportAllowUnknownHost(conf, U_("WineSupport"), U_("AllowUnknownHost"), false)
	, WineSupportEnablePulseAudio(conf, U_("WineSupport"), U_("EnablePulseAudio"), 1)
	, WineSupportEnablePortAudio(conf, U_("WineSupport"), U_("EnablePortAudio"), 1)
	, WineSupportEnableRtAudio(conf, U_("WineSupport"), U_("EnableRtAudio"), 1)
{

	// Effects
#ifndef NO_DSP
	m_MegaBassSettings.m_nXBassDepth = conf.Read<int32>(U_("Effects"), U_("XBassDepth"), m_MegaBassSettings.m_nXBassDepth);
	m_MegaBassSettings.m_nXBassRange = conf.Read<int32>(U_("Effects"), U_("XBassRange"), m_MegaBassSettings.m_nXBassRange);
#endif
#ifndef NO_REVERB
	m_ReverbSettings.m_nReverbDepth = conf.Read<int32>(U_("Effects"), U_("ReverbDepth"), m_ReverbSettings.m_nReverbDepth);
	m_ReverbSettings.m_nReverbType = conf.Read<int32>(U_("Effects"), U_("ReverbType"), m_ReverbSettings.m_nReverbType);
#endif
#ifndef NO_DSP
	m_SurroundSettings.m_nProLogicDepth = conf.Read<int32>(U_("Effects"), U_("ProLogicDepth"), m_SurroundSettings.m_nProLogicDepth);
	m_SurroundSettings.m_nProLogicDelay = conf.Read<int32>(U_("Effects"), U_("ProLogicDelay"), m_SurroundSettings.m_nProLogicDelay);
#endif
#ifndef NO_EQ
	m_EqSettings = conf.Read<EQPreset>(U_("Effects"), U_("EQ_Settings"), FlatEQPreset);
	const EQPreset userPresets[] =
	{
		FlatEQPreset,
		{ "User 1", {16,16,16,16,16,16}, { 150, 350, 700, 1500, 4500, 8000 } },
		{ "User 2", {16,16,16,16,16,16}, { 200, 400, 800, 1750, 5000, 9000 } },
		{ "User 3", {16,16,16,16,16,16}, { 250, 450, 900, 2000, 5000, 10000 } }
	};

	m_EqUserPresets[0] = conf.Read<EQPreset>(U_("Effects"), U_("EQ_User1"), userPresets[0]);
	m_EqUserPresets[1] = conf.Read<EQPreset>(U_("Effects"), U_("EQ_User2"), userPresets[1]);
	m_EqUserPresets[2] = conf.Read<EQPreset>(U_("Effects"), U_("EQ_User3"), userPresets[2]);
	m_EqUserPresets[3] = conf.Read<EQPreset>(U_("Effects"), U_("EQ_User4"), userPresets[3]);
#endif
#ifndef NO_DSP
	m_BitCrushSettings.m_Bits = conf.Read<int32>(U_("Effects"), U_("BitCrushBits"), m_BitCrushSettings.m_Bits);
#endif
	// Display (Colors)
	GetDefaultColourScheme(rgbCustomColors);
	for(int ncol = 0; ncol < MAX_MODCOLORS; ncol++)
	{
		const mpt::ustring colorName = MPT_UFORMAT("Color{}")(mpt::ufmt::dec0<2>(ncol));
		rgbCustomColors[ncol] = conf.Read<uint32>(U_("Display"), colorName, rgbCustomColors[ncol]);
	}
	// Paths
	m_szKbdFile = conf.Read<mpt::PathString>(U_("Paths"), U_("Key_Config_File"), mpt::PathString());
	conf.Forget(U_("Paths"), U_("Key_Config_File"));

	// init old and messy stuff:

	// Default chords
	MemsetZero(Chords);
	for(UINT ichord = 0; ichord < 3 * 12; ichord++)
	{
		Chords[ichord].key = (uint8)ichord;
		Chords[ichord].notes[0] = MPTChord::noNote;
		Chords[ichord].notes[1] = MPTChord::noNote;
		Chords[ichord].notes[2] = MPTChord::noNote;

		if(ichord < 12)
		{
			// Major Chords
			Chords[ichord].notes[0] = (int8)(ichord + 4);
			Chords[ichord].notes[1] = (int8)(ichord + 7);
			Chords[ichord].notes[2] = (int8)(ichord + 10);
		} else if(ichord < 24)
		{
			// Minor Chords
			Chords[ichord].notes[0] = (int8)(ichord - 9);
			Chords[ichord].notes[1] = (int8)(ichord - 5);
			Chords[ichord].notes[2] = (int8)(ichord - 2);
		}
	}


	// load old and messy stuff:

	PatternClipboard::SetClipboardSize(conf.Read<int32>(U_("Pattern Editor"), U_("NumClipboards"), mpt::saturate_cast<int32>(PatternClipboard::GetClipboardSize())));

	// Chords
	LoadChords(Chords);

	// Zxx Macros
	MIDIMacroConfig macros;
	theApp.GetDefaultMidiMacro(macros);
	for(int i = 0; i < kSFxMacros; i++)
	{
		macros.SFx[i] = conf.Read<std::string>(U_("Zxx Macros"), MPT_UFORMAT("SF{}")(mpt::ufmt::HEX(i)), macros.SFx[i]);
	}
	for(int i = 0; i < kZxxMacros; i++)
	{
		macros.Zxx[i] = conf.Read<std::string>(U_("Zxx Macros"), MPT_UFORMAT("Z{}")(mpt::ufmt::HEX0<2>(i | 0x80)), macros.Zxx[i]);
	}


	// MRU list
	Limit(mruListLength, 0u, 32u);
	mruFiles.reserve(mruListLength);
	for(uint32 i = 0; i < mruListLength; i++)
	{
		mpt::ustring key = MPT_UFORMAT("File{}")(i);

		mpt::PathString path = theApp.PathInstallRelativeToAbsolute(conf.Read<mpt::PathString>(U_("Recent File List"), key, mpt::PathString()));
		if(!path.empty())
		{
			mruFiles.push_back(path);
		}
	}

	// Fixups:
	// -------

	const Version storedVersion = PreviousSettingsVersion;

	// Version
	if(!VersionInstallGUID.Get().IsValid())
	{
		// No UUID found - generate one.
		VersionInstallGUID = mpt::UUID::Generate(mpt::global_prng());
	}

	// Plugins
	if(storedVersion < MPT_V("1.19.03.01") && vstHostProductString.Get() == "OpenMPT")
	{
		vstHostVendorVersion = Version::Current().GetRawVersion();
	}
	if(storedVersion < MPT_V("1.30.00.24"))
	{
		BrokenPluginsWorkaroundVSTNeverUnloadAnyPlugin = !conf.Read<bool>(U_("VST Plugins"), U_("FullyUnloadPlugins"), true);
		conf.Remove(U_("VST Plugins"), U_("FullyUnloadPlugins"));
	}

	// Sound Settings
	if(storedVersion < MPT_V("1.22.07.30"))
	{
		if(conf.Read<bool>(U_("Sound Settings"), U_("KeepDeviceOpen"), false))
		{
			m_SoundSettingsStopMode = SoundDeviceStopModePlaying;
		} else
		{
			m_SoundSettingsStopMode = SoundDeviceStopModeStopped;
		}
	}
	if(storedVersion < MPT_V("1.22.07.04"))
	{
		std::vector<uint32> sampleRates = m_SoundSampleRates;
		if(std::count(sampleRates.begin(), sampleRates.end(), MixerSamplerate) == 0)
		{
			sampleRates.push_back(MixerSamplerate);
			std::sort(sampleRates.begin(), sampleRates.end());
			std::reverse(sampleRates.begin(), sampleRates.end());
			m_SoundSampleRates = sampleRates;
		}
	}
	if(storedVersion < MPT_V("1.22.07.04"))
	{
		m_SoundDeviceID_DEPRECATED = conf.Read<SoundDevice::Legacy::ID>(U_("Sound Settings"), U_("WaveDevice"), SoundDevice::Legacy::ID());
		Setting<uint32> m_BufferLength_DEPRECATED(conf, U_("Sound Settings"), U_("BufferLength"), 50);
		Setting<uint32> m_LatencyMS(conf, U_("Sound Settings"), U_("Latency"), mpt::saturate_round<int32>(SoundDevice::Settings().Latency * 1000.0));
		Setting<uint32> m_UpdateIntervalMS(conf, U_("Sound Settings"), U_("UpdateInterval"), mpt::saturate_round<int32>(SoundDevice::Settings().UpdateInterval * 1000.0));
		Setting<SampleFormat> m_SampleFormat(conf, U_("Sound Settings"), U_("BitsPerSample"), SoundDevice::Settings().sampleFormat);
		Setting<bool> m_SoundDeviceExclusiveMode(conf, U_("Sound Settings"), U_("ExclusiveMode"), SoundDevice::Settings().ExclusiveMode);
		Setting<bool> m_SoundDeviceBoostThreadPriority(conf, U_("Sound Settings"), U_("BoostThreadPriority"), SoundDevice::Settings().BoostThreadPriority);
		Setting<bool> m_SoundDeviceUseHardwareTiming(conf, U_("Sound Settings"), U_("UseHardwareTiming"), SoundDevice::Settings().UseHardwareTiming);
		Setting<SoundDevice::ChannelMapping> m_SoundDeviceChannelMapping(conf, U_("Sound Settings"), U_("ChannelMapping"), SoundDevice::Settings().Channels);
		if(storedVersion < MPT_V("1.21.01.26"))
		{
			if(m_BufferLength_DEPRECATED != 0)
			{
				if(m_BufferLength_DEPRECATED < 1) m_BufferLength_DEPRECATED = 1; // 1ms
				if(m_BufferLength_DEPRECATED > 1000) m_BufferLength_DEPRECATED = 1000; // 1sec
				if((m_SoundDeviceID_DEPRECATED & SoundDevice::Legacy::MaskType) == SoundDevice::Legacy::TypeASIO)
				{
					m_LatencyMS = m_BufferLength_DEPRECATED * 1;
					m_UpdateIntervalMS = m_BufferLength_DEPRECATED / 8;
				} else
				{
					m_LatencyMS = m_BufferLength_DEPRECATED * 3;
					m_UpdateIntervalMS = m_BufferLength_DEPRECATED / 8;
				}
				if(!m_UpdateIntervalMS) m_UpdateIntervalMS = static_cast<uint32>(SoundDevice::Settings().UpdateInterval * 1000.0);
			}
			conf.Remove(m_BufferLength_DEPRECATED.GetPath());
		}
		if(storedVersion < MPT_V("1.22.01.03"))
		{
			m_SoundDeviceExclusiveMode = ((MixerFlags & OLD_SOUNDSETUP_SECONDARY) == 0);
		}
		if(storedVersion < MPT_V("1.22.01.03"))
		{
			m_SoundDeviceBoostThreadPriority = ((MixerFlags & OLD_SOUNDSETUP_NOBOOSTTHREADPRIORITY) == 0);
		}
		if(storedVersion < MPT_V("1.22.07.03"))
		{
			m_SoundDeviceChannelMapping = SoundDevice::ChannelMapping::BaseChannel(MixerOutputChannels, conf.Read<int>(U_("Sound Settings"), U_("ASIOBaseChannel"), 0));
		}
		m_SoundDeviceSettingsDefaults.Latency = m_LatencyMS / 1000.0;
		m_SoundDeviceSettingsDefaults.UpdateInterval = m_UpdateIntervalMS / 1000.0;
		m_SoundDeviceSettingsDefaults.Samplerate = MixerSamplerate;
		if(m_SoundDeviceSettingsDefaults.Channels.GetNumHostChannels() != MixerOutputChannels)
		{
			// reset invalid channel mapping to default
			m_SoundDeviceSettingsDefaults.Channels = SoundDevice::ChannelMapping(MixerOutputChannels);
		}
		m_SoundDeviceSettingsDefaults.InputChannels = 0;
		m_SoundDeviceSettingsDefaults.sampleFormat = m_SampleFormat;
		m_SoundDeviceSettingsDefaults.ExclusiveMode = m_SoundDeviceExclusiveMode;
		m_SoundDeviceSettingsDefaults.BoostThreadPriority = m_SoundDeviceBoostThreadPriority;
		m_SoundDeviceSettingsDefaults.UseHardwareTiming = m_SoundDeviceUseHardwareTiming;
		m_SoundDeviceSettingsDefaults.InputSourceID = 0;
		m_SoundDeviceSettingsUseOldDefaults = true;
	}
	if(storedVersion < MPT_V("1.28.00.41"))
	{
		// reset this setting to the default when updating,
		// because we do not provide a GUI any more,
		// and in general, it should not get changed anyway
		ResamplerCutoffPercent = mpt::saturate_round<int32>(CResamplerSettings().gdWFIRCutoff * 100.0);
	}
	if(MixerSamplerate == 0)
	{
		MixerSamplerate = MixerSettings().gdwMixingFreq;
	}
	if(storedVersion < MPT_V("1.21.01.26"))
	{
		MixerFlags &= ~OLD_SOUNDSETUP_REVERSESTEREO;
	}
	if(storedVersion < MPT_V("1.22.01.03"))
	{
		MixerFlags &= ~OLD_SOUNDSETUP_SECONDARY;
	}
	if(storedVersion < MPT_V("1.22.01.03"))
	{
		MixerFlags &= ~OLD_SOUNDSETUP_NOBOOSTTHREADPRIORITY;
	}
	if(storedVersion < MPT_V("1.20.00.22"))
	{
		MixerSettings settings = GetMixerSettings();
		settings.SetVolumeRampUpSamples(conf.Read<int32>(U_("Sound Settings"), U_("VolumeRampSamples"), 42));
		settings.SetVolumeRampDownSamples(conf.Read<int32>(U_("Sound Settings"), U_("VolumeRampSamples"), 42));
		SetMixerSettings(settings);
		conf.Remove(U_("Sound Settings"), U_("VolumeRampSamples"));
	} else if(storedVersion < MPT_V("1.22.07.18"))
	{
		MixerSettings settings = GetMixerSettings();
		settings.SetVolumeRampUpSamples(conf.Read<int32>(U_("Sound Settings"), U_("VolumeRampUpSamples"), MixerSettings().GetVolumeRampUpSamples()));
		settings.SetVolumeRampDownSamples(conf.Read<int32>(U_("Sound Settings"), U_("VolumeRampDownSamples"), MixerSettings().GetVolumeRampDownSamples()));
		SetMixerSettings(settings);
	}
	Limit(ResamplerCutoffPercent, 0, 100);
	if(storedVersion < MPT_V("1.29.00.11"))
	{
		MixerMaxChannels = MixerSettings().m_nMaxMixChannels;  // reset to default on update because we removed the setting in the GUI
	}
	if(storedVersion < MPT_V("1.29.00.20"))
	{
		MixerDSPMask = MixerDSPMask & ~SNDDSP_BITCRUSH;
	}

	// Misc
	if(defaultModType == MOD_TYPE_NONE)
	{
		defaultModType = MOD_TYPE_IT;
	}

	// MIDI Settings
	if((m_dwMidiSetup & 0x40) != 0 && storedVersion < MPT_V("1.20.00.86"))
	{
		// This flag used to be "amplify MIDI Note Velocity" - with a fixed amplification factor of 2.
		midiVelocityAmp = 200;
		m_dwMidiSetup &= ~0x40;
	}

	// Pattern Editor
	if(storedVersion < MPT_V("1.17.02.50"))
	{
		m_dwPatternSetup |= PATTERN_NOTEFADE;
	}
	if(storedVersion < MPT_V("1.17.03.01"))
	{
		m_dwPatternSetup |= PATTERN_RESETCHANNELS;
	}
	if(storedVersion < MPT_V("1.19.00.07"))
	{
		m_dwPatternSetup &= ~0x800;					// this was previously deprecated and is now used for something else
	}
	if(storedVersion < MPT_V("1.20.00.04"))
	{
		m_dwPatternSetup &= ~0x200000;				// ditto
	}
	if(storedVersion < MPT_V("1.20.00.07"))
	{
		m_dwPatternSetup &= ~0x400000;				// ditto
	}
	if(storedVersion < MPT_V("1.20.00.39"))
	{
		m_dwPatternSetup &= ~0x10000000;			// ditto
	}
	if(storedVersion < MPT_V("1.24.01.04"))
	{
		commentsFont = FontSetting(U_("Courier New"), (m_dwPatternSetup & 0x02) ? 120 : 90);
		patternFont = FontSetting((m_dwPatternSetup & 0x08) ? PATTERNFONT_SMALL : PATTERNFONT_LARGE, 0);
		m_dwPatternSetup &= ~(0x08 | 0x02);
	}
	if(storedVersion < MPT_V("1.25.00.08") && glGeneralWindowHeight < 222)
	{
		glGeneralWindowHeight += 44;
	}
	if(storedVersion < MPT_V("1.25.00.16") && (m_dwPatternSetup & 0x100000))
	{
		// Move MIDI recording to MIDI setup
		m_dwPatternSetup &= ~0x100000;
		m_dwMidiSetup |= MIDISETUP_ENABLE_RECORD_DEFAULT;
	}
	if(storedVersion < MPT_V("1.27.00.51"))
	{
		// Moving option out of pattern config
		CreateBackupFiles = (m_dwPatternSetup & 0x200) != 0;
		m_dwPatternSetup &= ~0x200;
	}

	// Export
	if(storedVersion < MPT_V("1.30.00.38"))
	{
		{
			conf.Write<Encoder::Mode>(U_("Export"), U_("FLAC_Mode"), Encoder::ModeLossless);
			const int oldformat = conf.Read<int>(U_("Export"), U_("FLAC_Format"), 1);
			Encoder::Format newformat = { Encoder::Format::Encoding::Integer, 24, mpt::get_endian() };
			if (oldformat >= 0)
			{
				switch (oldformat % 3)
				{
				case 0:
					newformat = { Encoder::Format::Encoding::Integer, 24, mpt::get_endian() };
					break;
				case 1:
					newformat = { Encoder::Format::Encoding::Integer, 16, mpt::get_endian() };
					break;
				case 2:
					newformat = { Encoder::Format::Encoding::Integer, 8, mpt::get_endian() };
					break;
				}
			}
			conf.Write<Encoder::Format>(U_("Export"), U_("FLAC_Format2"), newformat);
			conf.Forget(U_("Export"), U_("FLAC_Format"));
		}
		{
			conf.Write<Encoder::Mode>(U_("Export"), U_("Wave_Mode"), Encoder::ModeLossless);
			const int oldformat = conf.Read<int>(U_("Export"), U_("Wave_Format"), 1);
			Encoder::Format newformat = { Encoder::Format::Encoding::Float, 32, mpt::endian::little };
			if (oldformat >= 0)
			{
				switch (oldformat % 6)
				{
				case 0:
					newformat = { Encoder::Format::Encoding::Float, 64, mpt::endian::little };
					break;
				case 1:
					newformat = { Encoder::Format::Encoding::Float, 32, mpt::endian::little };
					break;
				case 2:
					newformat = { Encoder::Format::Encoding::Integer, 32, mpt::endian::little };
					break;
				case 3:
					newformat = { Encoder::Format::Encoding::Integer, 24, mpt::endian::little };
					break;
				case 4:
					newformat = { Encoder::Format::Encoding::Integer, 16, mpt::endian::little };
					break;
				case 5:
					newformat = { Encoder::Format::Encoding::Unsigned, 8, mpt::endian::little };
					break;
				}
			}
			conf.Write<Encoder::Format>(U_("Export"), U_("Wave_Format2"), newformat);
			conf.Forget(U_("Export"), U_("Wave_Format"));
		}
		{
			conf.Write<Encoder::Mode>(U_("Export"), U_("AU_Mode"), Encoder::ModeLossless);
			const int oldformat = conf.Read<int>(U_("Export"), U_("AU_Format"), 1);
			Encoder::Format newformat = { Encoder::Format::Encoding::Float, 32, mpt::endian::big };
			if(oldformat >= 0)
			{
				switch(oldformat % 6)
				{
				case 0:
					newformat = { Encoder::Format::Encoding::Float, 64, mpt::endian::big };
					break;
				case 1:
					newformat = { Encoder::Format::Encoding::Float, 32, mpt::endian::big };
					break;
				case 2:
					newformat = { Encoder::Format::Encoding::Integer, 32, mpt::endian::big };
					break;
				case 3:
					newformat = { Encoder::Format::Encoding::Integer, 24, mpt::endian::big };
					break;
				case 4:
					newformat = { Encoder::Format::Encoding::Integer, 16, mpt::endian::big };
					break;
				case 5:
					newformat = { Encoder::Format::Encoding::Integer, 8, mpt::endian::big };
					break;
				}
			}
			conf.Write<Encoder::Format>(U_("Export"), U_("AU_Format2"), newformat);
			conf.Forget(U_("Export"), U_("AU_Format"));
		}
		{
			conf.Write<Encoder::Mode>(U_("Export"), U_("RAW_Mode"), Encoder::ModeLossless);
			const int oldformat = conf.Read<int>(U_("Export"), U_("RAW_Format"), 1);
			Encoder::Format newformat = { Encoder::Format::Encoding::Float, 32, mpt::get_endian() };
			if(oldformat >= 0)
			{
				switch(oldformat % 7)
				{
				case 0:
					newformat = { Encoder::Format::Encoding::Float, 64, mpt::get_endian() };
					break;
				case 1:
					newformat = { Encoder::Format::Encoding::Float, 32, mpt::get_endian() };
					break;
				case 2:
					newformat = { Encoder::Format::Encoding::Integer, 32, mpt::get_endian() };
					break;
				case 3:
					newformat = { Encoder::Format::Encoding::Integer, 24, mpt::get_endian() };
					break;
				case 4:
					newformat = { Encoder::Format::Encoding::Integer, 16, mpt::get_endian() };
					break;
				case 5:
					newformat = { Encoder::Format::Encoding::Integer, 8, mpt::get_endian() };
					break;
				case 6:
					newformat = { Encoder::Format::Encoding::Unsigned, 8, mpt::get_endian() };
					break;
				}
			}
			conf.Write<Encoder::Format>(U_("Export"), U_("RAW_Format2"), newformat);
			conf.Forget(U_("Export"), U_("RAW_Format"));
		}
	}

#if defined(MPT_ENABLE_UPDATE)
	// Update
	if(storedVersion < MPT_V("1.28.00.39"))
	{
		if(UpdateUpdateCheckPeriod_DEPRECATED <= 0)
		{
			UpdateEnabled = true;
			UpdateIntervalDays = -1;
		} else
		{
			UpdateEnabled = true;
			UpdateIntervalDays = UpdateUpdateCheckPeriod_DEPRECATED.Get();
		}
		const auto url = UpdateUpdateURL_DEPRECATED.Get();
		if(url.empty() ||
		   url == UL_("http://update.openmpt.org/check/$VERSION/$GUID") ||
		   url == UL_("https://update.openmpt.org/check/$VERSION/$GUID"))
		{
			UpdateChannel = UpdateChannelRelease;
		} else if(url == UL_("http://update.openmpt.org/check/testing/$VERSION/$GUID") ||
		          url == UL_("https://update.openmpt.org/check/testing/$VERSION/$GUID"))
		{
			UpdateChannel = UpdateChannelDevelopment;
		} else
		{
			UpdateChannel = UpdateChannelDevelopment;
		}
		UpdateStatistics = UpdateSendGUID_DEPRECATED.Get();
		conf.Forget(UpdateUpdateCheckPeriod_DEPRECATED.GetPath());
		conf.Forget(UpdateUpdateURL_DEPRECATED.GetPath());
		conf.Forget(UpdateSendGUID_DEPRECATED.GetPath());
	}
#endif // MPT_ENABLE_UPDATE

	if(storedVersion < MPT_V("1.29.00.39"))
	{
		// ASIO device IDs are now normalized to upper-case in the device enumeration code.
		// Previous device IDs could be mixed-case (as retrieved from the registry), which would make direct ID comparison fail now.
		auto device = m_SoundDeviceIdentifier.Get();
		if(device.substr(0, 5) == UL_("ASIO_"))
		{
			device = mpt::ToUpperCase(device);
			m_SoundDeviceIdentifier = device;
		}
	}

	// Effects
#ifndef NO_EQ
	FixupEQ(m_EqSettings);
	FixupEQ(m_EqUserPresets[0]);
	FixupEQ(m_EqUserPresets[1]);
	FixupEQ(m_EqUserPresets[2]);
	FixupEQ(m_EqUserPresets[3]);
#endif // !NO_EQ

	// Zxx Macros
	if((MPT_V("1.17.00.00") <= storedVersion) && (storedVersion < MPT_V("1.20.00.00")))
	{
		// Fix old nasty broken (non-standard) MIDI configs in INI file.
		macros.UpgradeMacros();
	}
	theApp.SetDefaultMidiMacro(macros);

	// Paths
	m_szKbdFile = theApp.PathInstallRelativeToAbsolute(m_szKbdFile);

	// Sample undo buffer size (used to be a hidden, absolute setting in MiB)
	int64 oldUndoSize = m_SampleUndoBufferSize.Get().GetSizeInPercent();
	if(storedVersion < MPT_V("1.22.07.25") && oldUndoSize != SampleUndoBufferSize::defaultSize && oldUndoSize != 0)
	{
		m_SampleUndoBufferSize = SampleUndoBufferSize(static_cast<int32>(100 * (oldUndoSize << 20) / SampleUndoBufferSize(100).GetSizeInBytes()));
	}

	// More controls in the plugin selection dialog
	if(storedVersion < MPT_V("1.26.00.26"))
	{
		gnPlugWindowHeight += 40;
	}

	// Sanitize resampling mode for sample editor
	if(!Resampling::IsKnownMode(sampleEditorDefaultResampler) && sampleEditorDefaultResampler != SRCMODE_DEFAULT)
	{
		sampleEditorDefaultResampler = SRCMODE_DEFAULT;
	}

	// Migrate Tuning data
	MigrateTunings(storedVersion);

	// Sanitize MIDI import data
	if(midiImportPatternLen < 1 || midiImportPatternLen > MAX_PATTERN_ROWS)
		midiImportPatternLen = 128;
	if(midiImportQuantize < 4 || midiImportQuantize > 256)
		midiImportQuantize = 32;
	if(midiImportTicks < 2 || midiImportTicks > 16)
		midiImportTicks = 16;

	// Last fixup: update config version
	IniVersion = mpt::ufmt::val(Version::Current());

	// Write updated settings
	conf.Flush();
}


TrackerSettings::~TrackerSettings()
{
	return;
}


namespace SoundDevice
{
namespace Legacy
{
SoundDevice::Info FindDeviceInfo(SoundDevice::Manager &manager, SoundDevice::Legacy::ID id)
{
	if(manager.GetDeviceInfos().empty())
	{
		return SoundDevice::Info();
	}
	SoundDevice::Type type = SoundDevice::Type();
	switch((id & SoundDevice::Legacy::MaskType) >> SoundDevice::Legacy::ShiftType)
	{
		case SoundDevice::Legacy::TypeWAVEOUT:
			type = SoundDevice::TypeWAVEOUT;
			break;
		case SoundDevice::Legacy::TypeDSOUND:
			type = SoundDevice::TypeDSOUND;
			break;
		case SoundDevice::Legacy::TypeASIO:
			type = SoundDevice::TypeASIO;
			break;
		case SoundDevice::Legacy::TypePORTAUDIO_WASAPI:
			type = SoundDevice::TypePORTAUDIO_WASAPI;
			break;
		case SoundDevice::Legacy::TypePORTAUDIO_WDMKS:
			type = SoundDevice::TypePORTAUDIO_WDMKS;
			break;
		case SoundDevice::Legacy::TypePORTAUDIO_WMME:
			type = SoundDevice::TypePORTAUDIO_WMME;
			break;
		case SoundDevice::Legacy::TypePORTAUDIO_DS:
			type = SoundDevice::TypePORTAUDIO_DS;
			break;
	}
	if(type.empty())
	{	// fallback to first device
		return *manager.begin();
	}
	std::size_t index = static_cast<uint8>((id & SoundDevice::Legacy::MaskIndex) >> SoundDevice::Legacy::ShiftIndex);
	std::size_t seenDevicesOfDesiredType = 0;
	for(const auto &info : manager)
	{
		if(info.type == type)
		{
			if(seenDevicesOfDesiredType == index)
			{
				if(!info.IsValid())
				{	// fallback to first device
					return *manager.begin();
				}
				return info;
			}
			seenDevicesOfDesiredType++;
		}
	}
	// default to first device
	return *manager.begin();
}
} // namespace Legacy
} // namespace SoundDevice


void TrackerSettings::MigrateOldSoundDeviceSettings(SoundDevice::Manager &manager)
{
	if(m_SoundDeviceSettingsUseOldDefaults)
	{
		// get the old default device
		SetSoundDeviceIdentifier(SoundDevice::Legacy::FindDeviceInfo(manager, m_SoundDeviceID_DEPRECATED).GetIdentifier());
		// apply old global sound device settings to each found device
		for(const auto &it : manager)
		{
			SetSoundDeviceSettings(it.GetIdentifier(), GetSoundDeviceSettingsDefaults());
		}
	}
}


void TrackerSettings::MigrateTunings(const Version storedVersion)
{
	if(!PathTunings.GetDefaultDir().IsDirectory())
	{
		CreateDirectory(PathTunings.GetDefaultDir().AsNative().c_str(), 0);
	}
	if(!(PathTunings.GetDefaultDir() + P_("Built-in\\")).IsDirectory())
	{
		CreateDirectory((PathTunings.GetDefaultDir() + P_("Built-in\\")).AsNative().c_str(), 0);
	}
	if(!(PathTunings.GetDefaultDir() + P_("Locale\\")).IsDirectory())
	{
		CreateDirectory((PathTunings.GetDefaultDir() + P_("Local\\")).AsNative().c_str(), 0);
	}
	{
		mpt::PathString fn = PathTunings.GetDefaultDir() + P_("Built-in\\12TET.tun");
		if(!fn.FileOrDirectoryExists())
		{
			std::unique_ptr<CTuning> pT = CSoundFile::CreateTuning12TET(U_("12TET"));
			mpt::SafeOutputFile sf(fn, std::ios::binary, mpt::FlushMode::Full);
			pT->Serialize(sf);
		}
	}
	{
		mpt::PathString fn = PathTunings.GetDefaultDir() + P_("Built-in\\12TET [[fs15 1.17.02.49]].tun");
		if(!fn.FileOrDirectoryExists())
		{
			std::unique_ptr<CTuning> pT = CSoundFile::CreateTuning12TET(U_("12TET [[fs15 1.17.02.49]]"));
			mpt::SafeOutputFile sf(fn, std::ios::binary, mpt::FlushMode::Full);
			pT->Serialize(sf);
		}
	}
	oldLocalTunings = LoadLocalTunings();
	if(storedVersion < MPT_V("1.27.00.56"))
	{
		UnpackTuningCollection(*oldLocalTunings, PathTunings.GetDefaultDir() + P_("Local\\"));
	}
}


std::unique_ptr<CTuningCollection> TrackerSettings::LoadLocalTunings()
{
	std::unique_ptr<CTuningCollection> s_pTuningsSharedLocal = std::make_unique<CTuningCollection>();
	mpt::ifstream f(
			PathTunings.GetDefaultDir()
			+ P_("local_tunings")
			+ mpt::PathString::FromUTF8(CTuningCollection::s_FileExtension)
		, std::ios::binary);
	if(f.good())
	{
		mpt::ustring dummyName;
		s_pTuningsSharedLocal->Deserialize(f, dummyName, TuningCharsetFallback);
	}
	return s_pTuningsSharedLocal;
}


struct StoredSoundDeviceSettings
{

private:
	SettingsContainer &conf;
	const SoundDevice::Info deviceInfo;

private:
	Setting<uint32> LatencyUS;
	Setting<uint32> UpdateIntervalUS;
	Setting<uint32> Samplerate;
	Setting<uint8> ChannelsOld; // compatibility with older versions
	Setting<SoundDevice::ChannelMapping> ChannelMapping;
	Setting<uint8> InputChannels;
	Setting<SampleFormat> sampleFormat;
	Setting<bool> ExclusiveMode;
	Setting<bool> BoostThreadPriority;
	Setting<bool> KeepDeviceRunning;
	Setting<bool> UseHardwareTiming;
	Setting<int32> DitherType;
	Setting<uint32> InputSourceID;

public:

	StoredSoundDeviceSettings(SettingsContainer &conf, const SoundDevice::Info & deviceInfo, const SoundDevice::Settings & defaults)
		: conf(conf)
		, deviceInfo(deviceInfo)
		, LatencyUS(conf, U_("Sound Settings"), deviceInfo.GetIdentifier() + U_("_") + U_("Latency"), mpt::saturate_round<int32>(defaults.Latency * 1000000.0))
		, UpdateIntervalUS(conf, U_("Sound Settings"), deviceInfo.GetIdentifier() + U_("_") + U_("UpdateInterval"), mpt::saturate_round<int32>(defaults.UpdateInterval * 1000000.0))
		, Samplerate(conf, U_("Sound Settings"), deviceInfo.GetIdentifier() + U_("_") + U_("SampleRate"), defaults.Samplerate)
		, ChannelsOld(conf, U_("Sound Settings"), deviceInfo.GetIdentifier() + U_("_") + U_("Channels"), mpt::saturate_cast<uint8>((int)defaults.Channels))
		, ChannelMapping(conf, U_("Sound Settings"), deviceInfo.GetIdentifier() + U_("_") + U_("ChannelMapping"), defaults.Channels)
		, InputChannels(conf, U_("Sound Settings"), deviceInfo.GetIdentifier() + U_("_") + U_("InputChannels"), defaults.InputChannels)
		, sampleFormat(conf, U_("Sound Settings"), deviceInfo.GetIdentifier() + U_("_") + U_("SampleFormat"), defaults.sampleFormat)
		, ExclusiveMode(conf, U_("Sound Settings"), deviceInfo.GetIdentifier() + U_("_") + U_("ExclusiveMode"), defaults.ExclusiveMode)
		, BoostThreadPriority(conf, U_("Sound Settings"), deviceInfo.GetIdentifier() + U_("_") + U_("BoostThreadPriority"), defaults.BoostThreadPriority)
		, KeepDeviceRunning(conf, U_("Sound Settings"), deviceInfo.GetIdentifier() + U_("_") + U_("KeepDeviceRunning"), defaults.KeepDeviceRunning)
		, UseHardwareTiming(conf, U_("Sound Settings"), deviceInfo.GetIdentifier() + U_("_") + U_("UseHardwareTiming"), defaults.UseHardwareTiming)
		, DitherType(conf, U_("Sound Settings"), deviceInfo.GetIdentifier() + U_("_") + U_("DitherType"), static_cast<int32>(defaults.DitherType))
		, InputSourceID(conf, U_("Sound Settings"), deviceInfo.GetIdentifier() + U_("_") + U_("InputSourceID"), defaults.InputSourceID)
	{
		if(ChannelMapping.Get().GetNumHostChannels() != ChannelsOld)
		{
			// If the stored channel count and the count of channels used in the channel mapping do not match,
			// construct a default mapping from the channel count.
			ChannelMapping = SoundDevice::ChannelMapping(ChannelsOld);
		}
		// store informational data (not read back, just to allow the user to mock with the raw ini file)
		conf.Write(U_("Sound Settings"), deviceInfo.GetIdentifier() + U_("_") + U_("Type"), deviceInfo.type);
		conf.Write(U_("Sound Settings"), deviceInfo.GetIdentifier() + U_("_") + U_("InternalID"), deviceInfo.internalID);
		conf.Write(U_("Sound Settings"), deviceInfo.GetIdentifier() + U_("_") + U_("API"), deviceInfo.apiName);
		conf.Write(U_("Sound Settings"), deviceInfo.GetIdentifier() + U_("_") + U_("Name"), deviceInfo.name);
	}

	StoredSoundDeviceSettings & operator = (const SoundDevice::Settings &settings)
	{
		LatencyUS = mpt::saturate_round<int32>(settings.Latency * 1000000.0);
		UpdateIntervalUS = mpt::saturate_round<int32>(settings.UpdateInterval * 1000000.0);
		Samplerate = settings.Samplerate;
		ChannelsOld = mpt::saturate_cast<uint8>((int)settings.Channels);
		ChannelMapping = settings.Channels;
		InputChannels = settings.InputChannels;
		sampleFormat = settings.sampleFormat;
		ExclusiveMode = settings.ExclusiveMode;
		BoostThreadPriority = settings.BoostThreadPriority;
		KeepDeviceRunning = settings.KeepDeviceRunning;
		UseHardwareTiming = settings.UseHardwareTiming;
		DitherType = static_cast<int32>(settings.DitherType);
		InputSourceID = settings.InputSourceID;
		return *this;
	}

	operator SoundDevice::Settings () const
	{
		SoundDevice::Settings settings;
		settings.Latency = LatencyUS / 1000000.0;
		settings.UpdateInterval = UpdateIntervalUS / 1000000.0;
		settings.Samplerate = Samplerate;
		settings.Channels = ChannelMapping;
		settings.InputChannels = InputChannels;
		settings.sampleFormat = sampleFormat;
		settings.ExclusiveMode = ExclusiveMode;
		settings.BoostThreadPriority = BoostThreadPriority;
		settings.KeepDeviceRunning = KeepDeviceRunning;
		settings.UseHardwareTiming = UseHardwareTiming;
		settings.DitherType = DitherType;
		settings.InputSourceID = InputSourceID;
		return settings;
	}
};

SoundDevice::Settings TrackerSettings::GetSoundDeviceSettingsDefaults() const
{
	return m_SoundDeviceSettingsDefaults;
}

SoundDevice::Identifier TrackerSettings::GetSoundDeviceIdentifier() const
{
	return m_SoundDeviceIdentifier;
}

void TrackerSettings::SetSoundDeviceIdentifier(const SoundDevice::Identifier &identifier)
{
	m_SoundDeviceIdentifier = identifier;
}

SoundDevice::Settings TrackerSettings::GetSoundDeviceSettings(const SoundDevice::Identifier &device) const
{
	const SoundDevice::Info deviceInfo = theApp.GetSoundDevicesManager()->FindDeviceInfo(device);
	if(!deviceInfo.IsValid())
	{
		return SoundDevice::Settings();
	}
	const SoundDevice::Caps deviceCaps = theApp.GetSoundDevicesManager()->GetDeviceCaps(device, CMainFrame::GetMainFrame()->gpSoundDevice);
	SoundDevice::Settings settings = StoredSoundDeviceSettings(conf, deviceInfo, deviceCaps.DefaultSettings);
	return settings;
}

void TrackerSettings::SetSoundDeviceSettings(const SoundDevice::Identifier &device, const SoundDevice::Settings &settings)
{
	const SoundDevice::Info deviceInfo = theApp.GetSoundDevicesManager()->FindDeviceInfo(device);
	if(!deviceInfo.IsValid())
	{
		return;
	}
	const SoundDevice::Caps deviceCaps = theApp.GetSoundDevicesManager()->GetDeviceCaps(device, CMainFrame::GetMainFrame()->gpSoundDevice);
	StoredSoundDeviceSettings(conf, deviceInfo, deviceCaps.DefaultSettings) = settings;
}


MixerSettings TrackerSettings::GetMixerSettings() const
{
	MixerSettings settings;
	settings.m_nMaxMixChannels = MixerMaxChannels;
	settings.DSPMask = MixerDSPMask;
	settings.MixerFlags = MixerFlags;
	settings.gdwMixingFreq = MixerSamplerate;
	settings.gnChannels = MixerOutputChannels;
	settings.m_nPreAmp = MixerPreAmp;
	settings.m_nStereoSeparation = MixerStereoSeparation;
	settings.VolumeRampUpMicroseconds = MixerVolumeRampUpMicroseconds;
	settings.VolumeRampDownMicroseconds = MixerVolumeRampDownMicroseconds;
	settings.NumInputChannels = MixerNumInputChannels;
	return settings;
}

void TrackerSettings::SetMixerSettings(const MixerSettings &settings)
{
	MixerMaxChannels = settings.m_nMaxMixChannels;
	MixerDSPMask = settings.DSPMask;
	MixerFlags = settings.MixerFlags;
	MixerSamplerate = settings.gdwMixingFreq;
	MixerOutputChannels = settings.gnChannels;
	MixerPreAmp = settings.m_nPreAmp;
	MixerStereoSeparation = settings.m_nStereoSeparation;
	MixerVolumeRampUpMicroseconds = settings.VolumeRampUpMicroseconds;
	MixerVolumeRampDownMicroseconds = settings.VolumeRampDownMicroseconds;
	MixerNumInputChannels = static_cast<uint32>(settings.NumInputChannels);
}


CResamplerSettings TrackerSettings::GetResamplerSettings() const
{
	CResamplerSettings settings;
	settings.SrcMode = ResamplerMode;
	settings.gbWFIRType = ResamplerSubMode;
	settings.gdWFIRCutoff = ResamplerCutoffPercent * 0.01;
	settings.emulateAmiga = ResamplerEmulateAmiga;
	return settings;
}

void TrackerSettings::SetResamplerSettings(const CResamplerSettings &settings)
{
	ResamplerMode = settings.SrcMode;
	ResamplerSubMode = settings.gbWFIRType;
	ResamplerCutoffPercent = mpt::saturate_round<int32>(settings.gdWFIRCutoff * 100.0);
	ResamplerEmulateAmiga = settings.emulateAmiga;
}


void TrackerSettings::GetDefaultColourScheme(std::array<COLORREF, MAX_MODCOLORS> &colours)
{
	colours[MODCOLOR_BACKNORMAL] = RGB(0xFF, 0xFF, 0xFF);
	colours[MODCOLOR_TEXTNORMAL] = RGB(0x00, 0x00, 0x00);
	colours[MODCOLOR_BACKCURROW] = RGB(0xC0, 0xC0, 0xC0);
	colours[MODCOLOR_TEXTCURROW] = RGB(0x00, 0x00, 0x00);
	colours[MODCOLOR_BACKSELECTED] = RGB(0x00, 0x00, 0x00);
	colours[MODCOLOR_TEXTSELECTED] = RGB(0xFF, 0xFF, 0xFF);
	colours[MODCOLOR_SAMPLE] = RGB(0xFF, 0x00, 0x00);
	colours[MODCOLOR_BACKPLAYCURSOR] = RGB(0xFF, 0xFF, 0x80);
	colours[MODCOLOR_TEXTPLAYCURSOR] = RGB(0x00, 0x00, 0x00);
	colours[MODCOLOR_BACKHILIGHT] = RGB(0xE0, 0xE8, 0xE0);
	// Effect Colors
	colours[MODCOLOR_NOTE] = RGB(0x00, 0x00, 0x80);
	colours[MODCOLOR_INSTRUMENT] = RGB(0x00, 0x80, 0x80);
	colours[MODCOLOR_VOLUME] = RGB(0x00, 0x80, 0x00);
	colours[MODCOLOR_PANNING] = RGB(0x00, 0x80, 0x80);
	colours[MODCOLOR_PITCH] = RGB(0x80, 0x80, 0x00);
	colours[MODCOLOR_GLOBALS] = RGB(0x80, 0x00, 0x00);
	// VU-Meters
	colours[MODCOLOR_VUMETER_LO] = RGB(0x00, 0xC8, 0x00);
	colours[MODCOLOR_VUMETER_MED] = RGB(0xFF, 0xC8, 0x00);
	colours[MODCOLOR_VUMETER_HI] = RGB(0xE1, 0x00, 0x00);
	colours[MODCOLOR_VUMETER_LO_VST] = RGB(0x18, 0x96, 0xE1);
	colours[MODCOLOR_VUMETER_MED_VST] = RGB(0xFF, 0xC8, 0x00);
	colours[MODCOLOR_VUMETER_HI_VST] = RGB(0xE1, 0x00, 0x00);
	// Channel separators
	colours[MODCOLOR_SEPSHADOW] = GetSysColor(COLOR_BTNSHADOW);
	colours[MODCOLOR_SEPFACE] = GetSysColor(COLOR_BTNFACE);
	colours[MODCOLOR_SEPHILITE] = GetSysColor(COLOR_BTNHIGHLIGHT);
	// Pattern blend colour
	colours[MODCOLOR_BLENDCOLOR] = GetSysColor(COLOR_BTNFACE);
	// Dodgy commands
	colours[MODCOLOR_DODGY_COMMANDS] = RGB(0xC0, 0x00, 0x00);
	// Sample / instrument editor
	colours[MODCOLOR_BACKSAMPLE] = RGB(0x00, 0x00, 0x00);
	colours[MODCOLOR_SAMPLESELECTED] = RGB(0xFF, 0xFF, 0xFF);
	colours[MODCOLOR_BACKENV] = RGB(0x00, 0x00, 0x00);
	colours[MODCOLOR_ENVELOPES] = RGB(0x00, 0x00, 0xFF);
	colours[MODCOLOR_ENVELOPE_RELEASE] = RGB(0xFF, 0xFF, 0x00);
	colours[MODCOLOR_SAMPLE_LOOPMARKER] = RGB(0x30, 0xCC, 0x30);
	colours[MODCOLOR_SAMPLE_SUSTAINMARKER] = RGB(50, 0xCC, 0xCC);
	colours[MODCOLOR_SAMPLE_CUEPOINT] = RGB(0xFF, 0xCC, 0x30);
}


#ifndef NO_EQ

void TrackerSettings::FixupEQ(EQPreset &eqSettings)
{
	for(UINT i = 0; i < MAX_EQ_BANDS; i++)
	{
		if(eqSettings.Gains[i] > 32)
			eqSettings.Gains[i] = 16;
		if((eqSettings.Freqs[i] < 100) || (eqSettings.Freqs[i] > 10000))
			eqSettings.Freqs[i] = FlatEQPreset.Freqs[i];
	}
	mpt::String::SetNullTerminator(eqSettings.szName);
}

#endif // !NO_EQ


void TrackerSettings::SaveSettings()
{

	WINDOWPLACEMENT wpl;
	wpl.length = sizeof(WINDOWPLACEMENT);
	CMainFrame::GetMainFrame()->GetWindowPlacement(&wpl);
	conf.Write<WINDOWPLACEMENT>(U_("Display"), U_("WindowPlacement"), wpl);

	conf.Write<int32>(U_("Pattern Editor"), U_("NumClipboards"), mpt::saturate_cast<int32>(PatternClipboard::GetClipboardSize()));

	// Effects
#ifndef NO_DSP
	conf.Write<int32>(U_("Effects"), U_("XBassDepth"), m_MegaBassSettings.m_nXBassDepth);
	conf.Write<int32>(U_("Effects"), U_("XBassRange"), m_MegaBassSettings.m_nXBassRange);
#endif
#ifndef NO_REVERB
	conf.Write<int32>(U_("Effects"), U_("ReverbDepth"), m_ReverbSettings.m_nReverbDepth);
	conf.Write<int32>(U_("Effects"), U_("ReverbType"), m_ReverbSettings.m_nReverbType);
#endif
#ifndef NO_DSP
	conf.Write<int32>(U_("Effects"), U_("ProLogicDepth"), m_SurroundSettings.m_nProLogicDepth);
	conf.Write<int32>(U_("Effects"), U_("ProLogicDelay"), m_SurroundSettings.m_nProLogicDelay);
#endif
#ifndef NO_EQ
	conf.Write<EQPreset>(U_("Effects"), U_("EQ_Settings"), m_EqSettings);
	conf.Write<EQPreset>(U_("Effects"), U_("EQ_User1"), m_EqUserPresets[0]);
	conf.Write<EQPreset>(U_("Effects"), U_("EQ_User2"), m_EqUserPresets[1]);
	conf.Write<EQPreset>(U_("Effects"), U_("EQ_User3"), m_EqUserPresets[2]);
	conf.Write<EQPreset>(U_("Effects"), U_("EQ_User4"), m_EqUserPresets[3]);
#endif
#ifndef NO_DSP
	conf.Write<int32>(U_("Effects"), U_("BitCrushBits"), m_BitCrushSettings.m_Bits);
#endif

	// Display (Colors)
	for(int ncol = 0; ncol < MAX_MODCOLORS; ncol++)
	{
		conf.Write<uint32>(U_("Display"), MPT_UFORMAT("Color{}")(mpt::ufmt::dec0<2>(ncol)), rgbCustomColors[ncol]);
	}

	// Paths
	// Obsolete, since we always write to Keybindings.mkb now.
	// Older versions of OpenMPT 1.18+ will look for this file if this entry is missing, so removing this entry after having read it is kind of backwards compatible.
	conf.Remove(U_("Paths"), U_("Key_Config_File"));

	// Chords
	SaveChords(Chords);

	// Save default macro configuration
	MIDIMacroConfig macros;
	theApp.GetDefaultMidiMacro(macros);
	for(int isfx = 0; isfx < kSFxMacros; isfx++)
	{
		conf.Write<std::string>(U_("Zxx Macros"), MPT_UFORMAT("SF{}")(mpt::ufmt::HEX(isfx)), macros.SFx[isfx]);
	}
	for(int izxx = 0; izxx < kZxxMacros; izxx++)
	{
		conf.Write<std::string>(U_("Zxx Macros"), MPT_UFORMAT("Z{}")(mpt::ufmt::HEX0<2>(izxx | 0x80)), macros.Zxx[izxx]);
	}

	// MRU list
	for(uint32 i = 0; i < (ID_MRU_LIST_LAST - ID_MRU_LIST_FIRST + 1); i++)
	{
		mpt::ustring key = MPT_UFORMAT("File{}")(i);

		if(i < mruFiles.size())
		{
			mpt::PathString path = mruFiles[i];
			if(theApp.IsPortableMode())
			{
				path = theApp.PathAbsoluteToInstallRelative(path);
			}
			conf.Write<mpt::PathString>(U_("Recent File List"), key, path);
		} else
		{
			conf.Remove(U_("Recent File List"), key);
		}
	}
}


bool TrackerSettings::IsComponentBlocked(const std::string &key)
{
	return Setting<bool>(conf, U_("Components"), U_("Block") + mpt::ToUnicode(mpt::Charset::ASCII, key), ComponentManagerSettingsDefault().IsBlocked(key));
}


std::vector<uint32> TrackerSettings::GetSampleRates() const
{
	return m_SoundSampleRates;
}


std::vector<uint32> TrackerSettings::GetDefaultSampleRates()
{
	return std::vector<uint32>{
		192000,
		176400,
		96000,
		88200,
		48000,
		44100,
		32000,
		24000,
		22050,
		16000,
		11025,
		8000
	};
}


////////////////////////////////////////////////////////////////////////////////
// Chords

void TrackerSettings::LoadChords(MPTChords &chords)
{
	for(std::size_t i = 0; i < std::size(chords); i++)
	{
		uint32 chord;
		mpt::ustring noteName = MPT_UFORMAT("{}{}")(mpt::ustring(NoteNamesSharp[i % 12]), i / 12);
		if((chord = conf.Read<int32>(U_("Chords"), noteName, -1)) != uint32(-1))
		{
			if((chord & 0xFFFFFFC0) || chords[i].notes[0] == MPTChord::noNote)
			{
				chords[i].key = (uint8)(chord & 0x3F);
				int shift = 6;
				for(auto &note : chords[i].notes)
				{
					// Extract 6 bits and sign-extend to 8 bits
					const int signBit = ((chord >> (shift + 5)) & 1);
					note = static_cast<MPTChord::NoteType>(((chord >> shift) & 0x3F) | (0xC0 * signBit));
					shift += 6;
					if(note == 0)
						note = MPTChord::noNote;
					else if(note > 0)
						note--;
				}
			}
		}
	}
}


void TrackerSettings::SaveChords(MPTChords &chords)
{
	for(std::size_t i = 0; i < std::size(chords); i++)
	{
		auto notes = chords[i].notes;
		for(auto &note : notes)
		{
			if(note == MPTChord::noNote)
				note = 0;
			else if(note >= 0)
				note++;
			note &= 0x3F;
		}
		int32 s = (chords[i].key) | (notes[0] << 6) | (notes[1] << 12) | (notes[2] << 18);
		mpt::ustring noteName = MPT_UFORMAT("{}{}")(mpt::ustring(NoteNamesSharp[i % 12]), i / 12);
		conf.Write<int32>(U_("Chords"), noteName, s);
	}
}


void TrackerSettings::SetMIDIDevice(UINT id)
{
	m_nMidiDevice = id;
	MIDIINCAPS mic;
	mic.szPname[0] = 0;
	if(midiInGetDevCaps(id, &mic, sizeof(mic)) == MMSYSERR_NOERROR)
	{
		midiDeviceName = mic.szPname;
	}
}


UINT TrackerSettings::GetCurrentMIDIDevice()
{
	if(midiDeviceName.Get().IsEmpty())
		return m_nMidiDevice;

	CString deviceName = midiDeviceName;
	deviceName.TrimRight();

	MIDIINCAPS mic;
	UINT candidate = m_nMidiDevice, numDevs = midiInGetNumDevs();
	for(UINT i = 0; i < numDevs; i++)
	{
		mic.szPname[0] = 0;
		if(midiInGetDevCaps(i, &mic, sizeof(mic)) != MMSYSERR_NOERROR)
			continue;

		// Some device names have trailing spaces (e.g. "USB MIDI Interface "), but those may get lost in our settings framework.
		mpt::String::SetNullTerminator(mic.szPname);
		size_t strLen = _tcslen(mic.szPname);
		while(strLen-- > 0)
		{
			if(mic.szPname[strLen] == _T(' '))
				mic.szPname[strLen] = 0;
			else
				break;
		}
		if(CString(mic.szPname) == deviceName)
		{
			candidate = i;
			numDevs = m_nMidiDevice + 1;
			// If the same device name exists twice, try to match both device number and name
			if(candidate == m_nMidiDevice)
				return candidate;
		}
	}
	// If the device changed its ID, update it now.
	m_nMidiDevice = candidate;
	return candidate;
}


mpt::ustring IgnoredCCsToString(const std::bitset<128> &midiIgnoreCCs)
{
	mpt::ustring cc;
	bool first = true;
	for(int i = 0; i < 128; i++)
	{
		if(midiIgnoreCCs[i])
		{
			if(!first)
			{
				cc += U_(",");
			}
			cc += mpt::ufmt::val(i);
			first = false;
		}
	}
	return cc;
}


std::bitset<128> StringToIgnoredCCs(const mpt::ustring &in)
{
	CString cc = mpt::ToCString(in);
	std::bitset<128> midiIgnoreCCs;
	midiIgnoreCCs.reset();
	int curPos = 0;
	CString ccToken = cc.Tokenize(_T(", "), curPos);
	while(ccToken != _T(""))
	{
		int ccNumber = ConvertStrTo<int>(ccToken);
		if(ccNumber >= 0 && ccNumber <= 127)
			midiIgnoreCCs.set(ccNumber);
		ccToken = cc.Tokenize(_T(", "), curPos);
	}
	return midiIgnoreCCs;
}


DefaultAndWorkingDirectory::DefaultAndWorkingDirectory()
{
	return;
}

DefaultAndWorkingDirectory::DefaultAndWorkingDirectory(const mpt::PathString &def)
	: m_Default(def)
	, m_Working(def)
{
	return;
}

DefaultAndWorkingDirectory::~DefaultAndWorkingDirectory()
{
	return;
}

void DefaultAndWorkingDirectory::SetDefaultDir(const mpt::PathString &filenameFrom, bool stripFilename)
{
	if(InternalSet(m_Default, filenameFrom, stripFilename) && !m_Default.empty())
	{
		// When updating default directory, also update the working directory.
		InternalSet(m_Working, filenameFrom, stripFilename);
	}
}

void DefaultAndWorkingDirectory::SetWorkingDir(const mpt::PathString &filenameFrom, bool stripFilename)
{
	InternalSet(m_Working, filenameFrom, stripFilename);
}

mpt::PathString DefaultAndWorkingDirectory::GetDefaultDir() const
{
	return m_Default;
}

mpt::PathString DefaultAndWorkingDirectory::GetWorkingDir() const
{
	return m_Working;
}

// Retrieve / set default directory from given string and store it our setup variables
// If stripFilename is true, the filenameFrom parameter is assumed to be a full path including a filename.
// Return true if the value changed.
bool DefaultAndWorkingDirectory::InternalSet(mpt::PathString &dest, const mpt::PathString &filenameFrom, bool stripFilename)
{
	mpt::PathString newPath = (stripFilename ? filenameFrom.GetPath() : filenameFrom);
	newPath.EnsureTrailingSlash();
	mpt::PathString oldPath = dest;
	dest = newPath;
	return newPath != oldPath;
}

ConfigurableDirectory::ConfigurableDirectory(SettingsContainer &conf, const AnyStringLocale &section, const AnyStringLocale &key, const mpt::PathString &def)
	: conf(conf)
	, m_Setting(conf, section, key, def)
{
	SetDefaultDir(theApp.PathInstallRelativeToAbsolute(m_Setting), false);
}

ConfigurableDirectory::~ConfigurableDirectory()
{
	m_Setting = theApp.IsPortableMode() ? theApp.PathAbsoluteToInstallRelative(m_Default) : m_Default;
}



OPENMPT_NAMESPACE_END