Index: common/versionNumber.h
===================================================================
--- common/versionNumber.h	(revision 10676)
+++ common/versionNumber.h	(working copy)
@@ -21,7 +21,7 @@
 #define VER_MAJORMAJOR  1
 #define VER_MAJOR      28
 #define VER_MINOR      00
-#define VER_MINORMINOR 28
+#define VER_MINORMINOR 29
 
 //Numerical value of the version.
 #define MPT_VERSION_CURRENT MAKE_VERSION_NUMERIC(VER_MAJORMAJOR,VER_MAJOR,VER_MINOR,VER_MINORMINOR)
Index: mptrack/mptrack.rc
===================================================================
--- mptrack/mptrack.rc	(revision 10676)
+++ mptrack/mptrack.rc	(working copy)
@@ -244,22 +244,25 @@
 CAPTION "Update"
 FONT 8, "MS Shell Dlg", 400, 0, 0x1
 BEGIN
-    GROUPBOX        "Check for Updates",IDC_STATIC,6,6,276,66
-    CONTROL         "&Never",IDC_RADIO1,"Button",BS_AUTORADIOBUTTON,12,18,240,8
-    CONTROL         "&Daily",IDC_RADIO2,"Button",BS_AUTORADIOBUTTON,12,30,240,8
-    CONTROL         "&Weekly (recommended)",IDC_RADIO3,"Button",BS_AUTORADIOBUTTON,12,42,240,8
-    CONTROL         "&Monthly",IDC_RADIO4,"Button",BS_AUTORADIOBUTTON,12,54,240,8
-    GROUPBOX        "Privacy Settings",IDC_STATIC,6,78,276,54
-    CONTROL         "&Allow us to collect basic update statistics",IDC_CHECK1,
-                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,90,246,12
-    LTEXT           "If enabled, a randomized user ID is created and transmitted with every update check. This ID can not be linked to you or your computer in any way.",IDC_STATIC,12,102,264,24
-    GROUPBOX        "Advanced Settings",IDC_STATIC,6,138,276,60
-    LTEXT           "&Update server URL:",IDC_STATIC,12,150,186,8
-    EDITTEXT        IDC_EDIT1,12,162,264,12,ES_AUTOHSCROLL
-    PUSHBUTTON      "&Reset",IDC_BUTTON2,222,146,54,12
-    LTEXT           "Do not change this unless you are absolutely sure of what you are doing.",IDC_STATIC,12,180,264,12
-    PUSHBUTTON      "&Check for Updates",IDC_BUTTON1,6,204,84,18
-    LTEXT           "",IDC_LASTUPDATE,6,228,276,48
+    CONTROL         "Enable online Update &Check",IDC_CHECK_UPDATEENABLED,
+                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,6,105,10
+    GROUPBOX        "Update Channel",IDC_STATIC_UDATECHANNEL,6,18,276,54
+    CONTROL         "release: official stable released versions only (recommended)",IDC_RADIO1,
+                    "Button",BS_AUTORADIOBUTTON,12,30,211,10
+    CONTROL         "next: previews of the next official stable release",IDC_RADIO2,
+                    "Button",BS_AUTORADIOBUTTON,12,42,171,10
+    CONTROL         "development: bleeding-edge development versions",IDC_RADIO3,
+                    "Button",BS_AUTORADIOBUTTON,12,54,179,10
+    GROUPBOX        "Check for Updates",IDC_STATIC_UPDATECHECK,6,72,276,48
+    LTEXT           "&Automatically check on program start:",IDC_STATIC_UPDATEFREQUENCY,12,84,126,12,SS_CENTERIMAGE
+    COMBOBOX        IDC_COMBO_UPDATEFREQUENCY,138,84,42,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
+    PUSHBUTTON      "&Check now",IDC_BUTTON1,222,84,54,12
+    LTEXT           "",IDC_LASTUPDATE,12,102,264,12
+    GROUPBOX        "Privacy Settings",IDC_STATIC_UPDATEPRIVACY,6,120,276,156
+    CONTROL         "&Allow OpenMPT to collect basic statistics about your system configuration",IDC_CHECK1,
+                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,132,264,12
+    LTEXT           "",IDC_STATIC_UPDATEPRIVACYTEXT,12,144,264,36
+    EDITTEXT        IDC_EDIT_STATISTICS,12,180,264,90,ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_READONLY | WS_VSCROLL | WS_HSCROLL
 END
 
 IDD_CLOSEDOCUMENTS DIALOGEX 0, 0, 370, 197
@@ -881,7 +884,12 @@
     0
 END
 
+IDD_OPTIONS_UPDATE AFX_DIALOG_LAYOUT
+BEGIN
+    0
+END
 
+
 /////////////////////////////////////////////////////////////////////////////
 //
 // Dialog Info
Index: mptrack/resource.h
===================================================================
--- mptrack/resource.h	(revision 10676)
+++ mptrack/resource.h	(working copy)
@@ -973,6 +973,13 @@
 #define IDC_BUTTON_TUNING_REMOVE        2501
 #define IDC_STATIC_WINE_RTAUDIO         2502
 #define IDC_COMBO_WINE_RTAUDIO          2503
+#define IDC_STATIC_UPDATECHECK          2504
+#define IDC_STATIC_UPDATEPRIVACY        2505
+#define IDC_STATIC_UDATECHANNEL         2506
+#define IDC_COMBO_UPDATEFREQUENCY       2507
+#define IDC_STATIC_UPDATEFREQUENCY      2508
+#define IDC_CHECK_UPDATEENABLED         2509
+#define IDC_STATIC_UPDATEPRIVACYTEXT    2510
 #define ID_FILE_NEWMOD                  32771
 #define ID_FILE_NEWXM                   32772
 #define ID_FILE_NEWS3M                  32773
@@ -1267,9 +1274,9 @@
 #ifdef APSTUDIO_INVOKED
 #ifndef APSTUDIO_READONLY_SYMBOLS
 #define _APS_3D_CONTROLS                     1
-#define _APS_NEXT_RESOURCE_VALUE        542
+#define _APS_NEXT_RESOURCE_VALUE        543
 #define _APS_NEXT_COMMAND_VALUE         44646
-#define _APS_NEXT_CONTROL_VALUE         2504
+#define _APS_NEXT_CONTROL_VALUE         2511
 #define _APS_NEXT_SYMED_VALUE           901
 #endif
 #endif
Index: mptrack/TrackerSettings.cpp
===================================================================
--- mptrack/TrackerSettings.cpp	(revision 10676)
+++ mptrack/TrackerSettings.cpp	(working copy)
@@ -331,9 +331,16 @@
 	, vstHostVendorString(conf, MPT_USTRING("VST Plugins"), MPT_USTRING("HostVendorString"), "OpenMPT project")
 	, vstHostVendorVersion(conf, MPT_USTRING("VST Plugins"), MPT_USTRING("HostVendorVersion"), Version::Current().GetRawVersion())
 	// Update
+	, UpdateEnabled(conf, MPT_USTRING("Update"), MPT_USTRING("Enabled"), true)
 	, UpdateLastUpdateCheck(conf, MPT_USTRING("Update"), MPT_USTRING("LastUpdateCheck"), mpt::Date::Unix(time_t()))
-	, UpdateUpdateCheckPeriod(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateCheckPeriod"), 7)
-	, UpdateUpdateURL(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateURL"), CUpdateCheck::GetDefaultUpdateURL())
+	, UpdateUpdateCheckPeriod_DEPRECATED(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateCheckPeriod"), 7)
+	, UpdateIntervalDays(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateCheckPeriodDays"), 7)
+	, UpdateChannel(conf, MPT_USTRING("Update"), MPT_USTRING("Channel"), UpdateChannelRelease)
+	, UpdateUpdateURL_DEPRECATED(conf, MPT_USTRING("Update"), MPT_USTRING("UpdateURL"), CUpdateCheck::GetDefaultChannelReleaseURL())
+	, UpdateChannelReleaseURL(conf, MPT_USTRING("Update"), MPT_USTRING("ChannelReleaseURL"), CUpdateCheck::GetDefaultChannelReleaseURL())
+	, UpdateChannelNextURL(conf, MPT_USTRING("Update"), MPT_USTRING("ChannelStableURL"), CUpdateCheck::GetDefaultChannelNextURL())
+	, UpdateChannelDevelopmentURL(conf, MPT_USTRING("Update"), MPT_USTRING("ChannelDevelopmentURL"), CUpdateCheck::GetDefaultChannelDevelopmentURL())
+	, UpdateAPIURL(conf, MPT_USTRING("Update"), MPT_USTRING("APIURL"), CUpdateCheck::GetDefaultAPIURL())
 	, UpdateSendGUID(conf, MPT_USTRING("Update"), MPT_USTRING("SendGUID"), true)
 	, UpdateShowUpdateHint(conf, MPT_USTRING("Update"), MPT_USTRING("ShowUpdateHint"), true)
 	, UpdateSuggestDifferentBuildVariant(conf, MPT_USTRING("Update"), MPT_USTRING("SuggestDifferentBuildVariant"), true)
@@ -644,6 +651,45 @@
 		m_dwPatternSetup &= ~0x200;
 	}
 
+	// Update
+	if(storedVersion < MAKE_VERSION_NUMERIC(1,28,00,29))
+	{
+		if(UpdateUpdateCheckPeriod_DEPRECATED <= 0)
+		{
+			UpdateEnabled = true;
+			UpdateIntervalDays = -1;
+		} else
+		{
+			UpdateEnabled = true;
+			UpdateIntervalDays = UpdateUpdateCheckPeriod_DEPRECATED.Get();
+		}
+		if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING(""))
+		{
+			UpdateChannel = UpdateChannelRelease;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("http://www.soal.org/openmpt/OpenMPTversionCheck.php5"))
+		{
+			UpdateChannel = UpdateChannelRelease;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("http://update.openmpt.org/check/$VERSION/$GUID"))
+		{
+			UpdateChannel = UpdateChannelRelease;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("https://update.openmpt.org/check/$VERSION/$GUID"))
+		{
+			UpdateChannel = UpdateChannelRelease;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("http://update.openmpt.org/check/testing/$VERSION/$GUID"))
+		{
+			UpdateChannel = UpdateChannelDevelopment;
+		} else if(UpdateUpdateURL_DEPRECATED.Get() == MPT_USTRING("https://update.openmpt.org/check/testing/$VERSION/$GUID"))
+		{
+			UpdateChannel = UpdateChannelDevelopment;
+		} else
+		{
+			UpdateChannel = UpdateChannelDevelopment;
+			UpdateChannelDevelopmentURL = UpdateUpdateURL_DEPRECATED.Get();
+		}
+		conf.Forget(UpdateUpdateCheckPeriod_DEPRECATED.GetPath());
+		conf.Forget(UpdateUpdateURL_DEPRECATED.GetPath());
+	}
+
 	// Effects
 #ifndef NO_EQ
 	FixupEQ(m_EqSettings);
Index: mptrack/TrackerSettings.h
===================================================================
--- mptrack/TrackerSettings.h	(revision 10676)
+++ mptrack/TrackerSettings.h	(working copy)
@@ -817,9 +817,16 @@
 
 	// Update
 
+	Setting<bool> UpdateEnabled;
 	Setting<mpt::Date::Unix> UpdateLastUpdateCheck;
-	Setting<int32> UpdateUpdateCheckPeriod;
-	Setting<mpt::ustring> UpdateUpdateURL;
+	Setting<int32> UpdateUpdateCheckPeriod_DEPRECATED;
+	Setting<int32> UpdateIntervalDays;
+	Setting<uint32> UpdateChannel;
+	Setting<mpt::ustring> UpdateUpdateURL_DEPRECATED;
+	Setting<mpt::ustring> UpdateChannelReleaseURL;
+	Setting<mpt::ustring> UpdateChannelNextURL;
+	Setting<mpt::ustring> UpdateChannelDevelopmentURL;
+	Setting<mpt::ustring> UpdateAPIURL;
 	Setting<bool> UpdateSendGUID;
 	Setting<bool> UpdateShowUpdateHint;
 	Setting<bool> UpdateSuggestDifferentBuildVariant;
Index: mptrack/UpdateCheck.cpp
===================================================================
--- mptrack/UpdateCheck.cpp	(revision 10676)
+++ mptrack/UpdateCheck.cpp	(working copy)
@@ -13,6 +13,7 @@
 #include "BuildVariants.h"
 #include "../common/version.h"
 #include "../common/misc_util.h"
+#include "../common/mptStringBuffer.h"
 #include "Mptrack.h"
 #include "TrackerSettings.h"
 // Setup dialog stuff
@@ -19,6 +20,7 @@
 #include "Mainfrm.h"
 #include "../common/mptThread.h"
 #include "HTTP.h"
+#include "../misc/JSON.h"
 
 
 OPENMPT_NAMESPACE_BEGIN
@@ -84,12 +86,28 @@
 
 
 
-mpt::ustring CUpdateCheck::GetDefaultUpdateURL()
+mpt::ustring CUpdateCheck::GetDefaultChannelReleaseURL()
 {
 	return MPT_USTRING("https://update.openmpt.org/check/$VERSION/$GUID");
 }
 
+mpt::ustring CUpdateCheck::GetDefaultChannelNextURL()
+{
+	return MPT_USTRING("https://update.openmpt.org/check/next/$VERSION/$GUID");
+}
 
+mpt::ustring CUpdateCheck::GetDefaultChannelDevelopmentURL()
+{
+	return MPT_USTRING("https://update.openmpt.org/check/testing/$VERSION/$GUID");
+}
+
+
+mpt::ustring CUpdateCheck::GetDefaultAPIURL()
+{
+	return MPT_USTRING("https://update.openmpt.org/api/v3/");
+}
+
+
 std::atomic<int32> CUpdateCheck::s_InstanceCount(0);
 
 
@@ -104,11 +122,15 @@
 {
 	if(isAutoUpdate)
 	{
-		int updateCheckPeriod = TrackerSettings::Instance().UpdateUpdateCheckPeriod;
-		if(updateCheckPeriod == 0)
+		if(!TrackerSettings::Instance().UpdateEnabled)
 		{
 			return;
 		}
+		int updateCheckPeriod = TrackerSettings::Instance().UpdateIntervalDays;
+		if(updateCheckPeriod < 0)
+		{
+			updateCheckPeriod = 0;
+		}
 		// Do we actually need to run the update check right now?
 		const time_t now = time(nullptr);
 		if(difftime(now, TrackerSettings::Instance().UpdateLastUpdateCheck.Get()) < (double)(updateCheckPeriod * 86400))
@@ -121,7 +143,7 @@
 		{
 			TrackerSettings::Instance().UpdateShowUpdateHint = false;
 			CString msg;
-			msg.Format(_T("OpenMPT would like to check for updates now, proceed?\n\nNote: In the future, OpenMPT will check for updates every %u days. If you do not want this, you can disable update checks in the setup."), TrackerSettings::Instance().UpdateUpdateCheckPeriod.Get());
+			msg.Format(_T("OpenMPT would like to check for updates now, proceed?\n\nNote: In the future, OpenMPT will check for updates every %u days. If you do not want this, you can disable update checks in the setup."), TrackerSettings::Instance().UpdateIntervalDays.Get());
 			if(Reporting::Confirm(msg, _T("OpenMPT Internet Update")) == cnfNo)
 			{
 				TrackerSettings::Instance().UpdateLastUpdateCheck = mpt::Date::Unix(now);
@@ -128,6 +150,15 @@
 				return;
 			}
 		}
+	} else
+	{
+		if(!TrackerSettings::Instance().UpdateEnabled)
+		{
+			if(Reporting::Confirm(_T("Update Check is disabled. Do you want to check anyway?"), _T("OpenMPT Internet Update")) != cnfYes)
+			{
+				return;
+			}
+		}
 	}
 	TrackerSettings::Instance().UpdateShowUpdateHint = false;
 
@@ -137,22 +168,33 @@
 		return;
 	}
 
-	CUpdateCheck::Settings settings;
-	settings.window = CMainFrame::GetMainFrame();
-	settings.msgProgress = MPT_WM_APP_UPDATECHECK_PROGRESS;
-	settings.msgSuccess = MPT_WM_APP_UPDATECHECK_SUCCESS;
-	settings.msgFailure = MPT_WM_APP_UPDATECHECK_FAILURE;
-	settings.autoUpdate = isAutoUpdate;
-	settings.updateBaseURL = TrackerSettings::Instance().UpdateUpdateURL;
-	settings.sendStatistics = TrackerSettings::Instance().UpdateSendGUID;
-	settings.statisticsUUID = TrackerSettings::Instance().VersionInstallGUID;
-	settings.suggestDifferentBuilds = TrackerSettings::Instance().UpdateSuggestDifferentBuildVariant;
-	std::thread(CUpdateCheck::ThreadFunc(settings)).detach();
+	CUpdateCheck::Context context;
+	context.window = CMainFrame::GetMainFrame();
+	context.msgProgress = MPT_WM_APP_UPDATECHECK_PROGRESS;
+	context.msgSuccess = MPT_WM_APP_UPDATECHECK_SUCCESS;
+	context.msgFailure = MPT_WM_APP_UPDATECHECK_FAILURE;
+	context.autoUpdate = isAutoUpdate;
+	std::thread(CUpdateCheck::ThreadFunc(CUpdateCheck::Settings(), context)).detach();
 }
 
 
-CUpdateCheck::ThreadFunc::ThreadFunc(const CUpdateCheck::Settings &settings)
+CUpdateCheck::Settings::Settings()
+	: periodDays(TrackerSettings::Instance().UpdateIntervalDays)
+	, channel(TrackerSettings::Instance().UpdateChannel)
+	, channelReleaseURL(TrackerSettings::Instance().UpdateChannelReleaseURL)
+	, channelNextURL(TrackerSettings::Instance().UpdateChannelNextURL)
+	, channelDevelopmentURL(TrackerSettings::Instance().UpdateChannelDevelopmentURL)
+	, apiURL(TrackerSettings::Instance().UpdateAPIURL)
+	, sendStatistics(TrackerSettings::Instance().UpdateSendGUID)
+	, statisticsUUID(TrackerSettings::Instance().VersionInstallGUID)
+	, suggestDifferentBuilds(TrackerSettings::Instance().UpdateSuggestDifferentBuildVariant)
+{
+}
+
+
+CUpdateCheck::ThreadFunc::ThreadFunc(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context)
 	: settings(settings)
+	, context(context)
 {
 	return;
 }
@@ -160,27 +202,104 @@
 
 void CUpdateCheck::ThreadFunc::operator () ()
 {
-	mpt::SetCurrentThreadPriority(settings.autoUpdate ? mpt::ThreadPriorityLower : mpt::ThreadPriorityNormal);
-	CUpdateCheck::CheckForUpdate(settings);
+	mpt::SetCurrentThreadPriority(context.autoUpdate ? mpt::ThreadPriorityLower : mpt::ThreadPriorityNormal);
+	CUpdateCheck::CheckForUpdate(settings, context);
 }
 
 
-// Run update check (independent thread)
-CUpdateCheck::Result CUpdateCheck::SearchUpdate(const CUpdateCheck::Settings &settings)
+std::string CUpdateCheck::GetStatisticsDataV3(const Settings &settings)
 {
-	
-	HTTP::InternetSession internet(Version::Current().GetOpenMPTVersionString());
+	JSON::value j;
+	j["OpenMPT"]["Version"] = mpt::ufmt::val(Version::Current());
+	j["OpenMPT"]["BuildVariant"] = BuildVariants().GuessCurrentBuildName();
+	j["OpenMPT"]["Architecture"] = mpt::Windows::Name(mpt::Windows::GetProcessArchitecture());
+	j["Update"]["PeriodDays"] = settings.periodDays;
+	j["System"]["Windows"]["Version"]["Name"] = mpt::Windows::Version::Current().GetName();
+	j["System"]["Windows"]["Version"]["Major"] = mpt::Windows::Version::Current().GetSystem().Major;
+	j["System"]["Windows"]["Version"]["Minor"] = mpt::Windows::Version::Current().GetSystem().Minor;
+	j["System"]["Windows"]["ServicePack"]["Major"] = mpt::Windows::Version::Current().GetServicePack().Major;
+	j["System"]["Windows"]["ServicePack"]["Minor"] = mpt::Windows::Version::Current().GetServicePack().Minor;
+	j["System"]["Windows"]["Build"] = mpt::Windows::Version::Current().GetBuild();
+	j["System"]["Windows"]["IsWine"] = mpt::Windows::IsWine();
+	if(mpt::Windows::IsWine())
+	{
+		mpt::Wine::VersionContext v;
+		j["System"]["Windows"]["Wine"]["Version"]["Raw"] = v.RawVersion();
+		if(v.Version().IsValid())
+		{
+			j["System"]["Windows"]["Wine"]["Version"]["Major"] = v.Version().GetMajor();
+			j["System"]["Windows"]["Wine"]["Version"]["Minor"] = v.Version().GetMinor();
+			j["System"]["Windows"]["Wine"]["Version"]["Update"] = v.Version().GetUpdate();
+		}
+		j["System"]["Windows"]["Wine"]["HostSysName"] = v.RawHostSysName();
+	}
+	j["System"]["Architecture"] = mpt::Windows::Name(mpt::Windows::GetHostArchitecture());
+	MEMORYSTATUSEX memoryStatus;
+	MemsetZero(memoryStatus);
+	memoryStatus.dwLength = sizeof(MEMORYSTATUSEX);
+	if(GlobalMemoryStatusEx(&memoryStatus) != 0)
+	{
+		j["System"]["Memory"] = memoryStatus.ullTotalPhys / 1024 / 1024;
+	}
+	#ifdef ENABLE_ASM
+		j["System"]["Processor"]["Vendor"] = std::string(mpt::String::ReadAutoBuf(ProcVendorID));
+		j["System"]["Processor"]["Brand"] = std::string(mpt::String::ReadAutoBuf(ProcBrandID));
+		j["System"]["Processor"]["Family"] = ProcFamily;
+		j["System"]["Processor"]["Model"] = ProcModel;
+		j["System"]["Processor"]["Stepping"] = ProcStepping;
+		j["System"]["Processor"]["Features"]["tsc"] = ((GetRealProcSupport() & PROCSUPPORT_TSC) ? true : false);
+		j["System"]["Processor"]["Features"]["cmov"] = ((GetRealProcSupport() & PROCSUPPORT_CMOV) ? true : false);
+		j["System"]["Processor"]["Features"]["mmx"] = ((GetRealProcSupport() & PROCSUPPORT_MMX) ? true : false);
+		j["System"]["Processor"]["Features"]["mmxext"] = ((GetRealProcSupport() & PROCSUPPORT_AMD_MMXEXT) ? true : false);
+		j["System"]["Processor"]["Features"]["3dnow"] = ((GetRealProcSupport() & PROCSUPPORT_AMD_3DNOW) ? true : false);
+		j["System"]["Processor"]["Features"]["3dnowext"] = ((GetRealProcSupport() & PROCSUPPORT_AMD_3DNOWEXT) ? true : false);
+		j["System"]["Processor"]["Features"]["sse"] = ((GetRealProcSupport() & PROCSUPPORT_SSE) ? true : false);
+		j["System"]["Processor"]["Features"]["sse2"] = ((GetRealProcSupport() & PROCSUPPORT_SSE2) ? true : false);
+		j["System"]["Processor"]["Features"]["sse3"] = ((GetRealProcSupport() & PROCSUPPORT_SSE3) ? true : false);
+		j["System"]["Processor"]["Features"]["ssse3"] = ((GetRealProcSupport() & PROCSUPPORT_SSSE3) ? true : false);
+		j["System"]["Processor"]["Features"]["sse4_1"] = ((GetRealProcSupport() & PROCSUPPORT_SSE4_1) ? true : false);
+		j["System"]["Processor"]["Features"]["sse4_2"] = ((GetRealProcSupport() & PROCSUPPORT_SSE4_2) ? true : false);
+	#endif
+	return j.dump(1, '\t');
+}
 
-	mpt::ustring updateURL = settings.updateBaseURL;
-	if(updateURL.empty())
+
+mpt::ustring CUpdateCheck::GetUpdateURLV2(const CUpdateCheck::Settings &settings)
+{
+	mpt::ustring updateURL;
+	if(settings.channel == UpdateChannelRelease)
 	{
-		updateURL = GetDefaultUpdateURL();
+		updateURL = settings.channelReleaseURL;
+		if(updateURL.empty())
+		{
+			updateURL = GetDefaultChannelReleaseURL();
+		}
+	}	else if(settings.channel == UpdateChannelNext)
+	{
+		updateURL = settings.channelNextURL;
+		if(updateURL.empty())
+		{
+			updateURL = GetDefaultChannelNextURL();
+		}
+	}	else if(settings.channel == UpdateChannelDevelopment)
+	{
+		updateURL = settings.channelDevelopmentURL;
+		if(updateURL.empty())
+		{
+			updateURL = GetDefaultChannelDevelopmentURL();
+		}
+	}	else
+	{
+		updateURL = settings.channelReleaseURL;
+		if(updateURL.empty())
+		{
+			updateURL = GetDefaultChannelReleaseURL();
+		}
 	}
 	if(updateURL.find(MPT_USTRING("://")) == mpt::ustring::npos)
 	{
 		updateURL = MPT_USTRING("https://") + updateURL;
 	}
-
 	// Build update URL
 	updateURL = mpt::String::Replace(updateURL, MPT_USTRING("$VERSION"), mpt::uformat(MPT_USTRING("%1-%2-%3"))
 		( Version::Current()
@@ -188,15 +307,44 @@
 		, settings.sendStatistics ? mpt::Windows::Version::Current().GetNameShort() : MPT_USTRING("unknown")
 		));
 	updateURL = mpt::String::Replace(updateURL, MPT_USTRING("$GUID"), settings.sendStatistics ? mpt::ufmt::val(settings.statisticsUUID) : MPT_USTRING("anonymous"));
+	return updateURL;
+}
 
+
+// Run update check (independent thread)
+CUpdateCheck::Result CUpdateCheck::SearchUpdate(const CUpdateCheck::Settings &settings)
+{
+	
+	HTTP::InternetSession internet(Version::Current().GetOpenMPTVersionString());
+
 	// Establish a connection.
 	HTTP::Request request;
-	request.SetURI(ParseURI(updateURL));
+	request.SetURI(ParseURI(GetUpdateURLV2(settings)));
 	request.method = HTTP::Method::Get;
 	request.flags = HTTP::NoCache;
 
 	HTTP::Result resultHTTP = internet(request.InsecureTLSDowngradeWindowsXP());
 
+	if(settings.sendStatistics)
+	{
+		HTTP::Request requestStatistics;
+		if(settings.statisticsUUID.IsValid())
+		{
+			requestStatistics.SetURI(ParseURI(settings.apiURL + mpt::format(MPT_USTRING("statistics/%1"))(settings.statisticsUUID)));
+			requestStatistics.method = HTTP::Method::Put;
+		} else
+		{
+			requestStatistics.SetURI(ParseURI(settings.apiURL + MPT_USTRING("statistics/")));
+			requestStatistics.method = HTTP::Method::Post;
+		}
+		requestStatistics.dataMimeType = HTTP::MimeType::JSON();
+		requestStatistics.acceptMimeTypes = HTTP::MimeTypes::JSON();
+		std::string jsondata = GetStatisticsDataV3(settings);
+		MPT_LOG(LogInformation, "Update", mpt::ToUnicode(mpt::CharsetUTF8, jsondata));
+		requestStatistics.data = mpt::byte_cast<mpt::const_byte_span>(mpt::as_span(jsondata));
+		internet(requestStatistics.InsecureTLSDowngradeWindowsXP());
+	}
+
 	// Retrieve HTTP status code.
 	if(resultHTTP.Status >= 400)
 	{
@@ -244,12 +392,12 @@
 }
 
 
-void CUpdateCheck::CheckForUpdate(const CUpdateCheck::Settings &settings)
+void CUpdateCheck::CheckForUpdate(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context)
 {
 	// íncremented before starting the thread
 	MPT_ASSERT(s_InstanceCount.load() >= 1);
 	CUpdateCheck::Result result;
-	settings.window->SendMessage(settings.msgProgress, settings.autoUpdate ? 1 : 0, s_InstanceCount.load());
+	context.window->SendMessage(context.msgProgress, context.autoUpdate ? 1 : 0, s_InstanceCount.load());
 	try
 	{
 		try
@@ -264,12 +412,12 @@
 		}
 	} catch(const CUpdateCheck::Error &e)
 	{
-		settings.window->SendMessage(settings.msgFailure, settings.autoUpdate ? 1 : 0, reinterpret_cast<LPARAM>(&e));
+		context.window->SendMessage(context.msgFailure, context.autoUpdate ? 1 : 0, reinterpret_cast<LPARAM>(&e));
 		s_InstanceCount.fetch_sub(1);
 		MPT_ASSERT(s_InstanceCount.load() >= 0);
 		return;
 	}
-	settings.window->SendMessage(settings.msgSuccess, settings.autoUpdate ? 1 : 0, reinterpret_cast<LPARAM>(&result));
+	context.window->SendMessage(context.msgSuccess, context.autoUpdate ? 1 : 0, reinterpret_cast<LPARAM>(&result));
 	s_InstanceCount.fetch_sub(1);
 	MPT_ASSERT(s_InstanceCount.load() >= 0);
 }
@@ -347,14 +495,13 @@
 // CUpdateSetupDlg
 
 BEGIN_MESSAGE_MAP(CUpdateSetupDlg, CPropertyPage)
-	ON_COMMAND(IDC_BUTTON1,			&CUpdateSetupDlg::OnCheckNow)
-	ON_COMMAND(IDC_BUTTON2,			&CUpdateSetupDlg::OnResetURL)
-	ON_COMMAND(IDC_RADIO1,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_COMMAND(IDC_RADIO2,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_COMMAND(IDC_RADIO3,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_COMMAND(IDC_RADIO4,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_COMMAND(IDC_CHECK1,			&CUpdateSetupDlg::OnSettingsChanged)
-	ON_EN_CHANGE(IDC_EDIT1,			&CUpdateSetupDlg::OnSettingsChanged)
+	ON_COMMAND(IDC_CHECK_UPDATEENABLED,         &CUpdateSetupDlg::OnSettingsChanged)
+	ON_COMMAND(IDC_RADIO1,                      &CUpdateSetupDlg::OnSettingsChanged)
+	ON_COMMAND(IDC_RADIO2,                      &CUpdateSetupDlg::OnSettingsChanged)
+	ON_COMMAND(IDC_RADIO3,                      &CUpdateSetupDlg::OnSettingsChanged)
+	ON_COMMAND(IDC_BUTTON1,                     &CUpdateSetupDlg::OnCheckNow)
+	ON_CBN_SELCHANGE(IDC_COMBO_UPDATEFREQUENCY, &CUpdateSetupDlg::OnSettingsChanged)
+	ON_COMMAND(IDC_CHECK1,                      &CUpdateSetupDlg::OnSettingsChanged)
 END_MESSAGE_MAP()
 
 
@@ -366,29 +513,82 @@
 }
 
 
+void CUpdateSetupDlg::DoDataExchange(CDataExchange *pDX)
+{
+	CDialog::DoDataExchange(pDX);
+	DDX_Control(pDX, IDC_COMBO_UPDATEFREQUENCY, m_CbnUpdateFrequency);
+}
+
+
 BOOL CUpdateSetupDlg::OnInitDialog()
 {
 	CPropertyPage::OnInitDialog();
 
+	CheckDlgButton(IDC_CHECK_UPDATEENABLED, TrackerSettings::Instance().UpdateEnabled ? BST_CHECKED : BST_UNCHECKED);
+
 	int radioID = 0;
-	int periodDays = TrackerSettings::Instance().UpdateUpdateCheckPeriod;
-	if(periodDays >= 30)
+	int updateChannel = TrackerSettings::Instance().UpdateChannel;
+	if(updateChannel == UpdateChannelRelease)
 	{
-		radioID = IDC_RADIO4;
-	} else if(periodDays >= 7)
+		radioID = IDC_RADIO1;
+	} else if(updateChannel == UpdateChannelNext)
 	{
+		radioID = IDC_RADIO2;
+	} else if(updateChannel == UpdateChannelDevelopment)
+	{
 		radioID = IDC_RADIO3;
-	} else if(periodDays >= 1)
-	{
-		radioID = IDC_RADIO2;
 	} else
 	{
 		radioID = IDC_RADIO1;
 	}
-	CheckRadioButton(IDC_RADIO1, IDC_RADIO4, radioID);
+	CheckRadioButton(IDC_RADIO1, IDC_RADIO3, radioID);
+
+	int32 periodDays = TrackerSettings::Instance().UpdateIntervalDays;
+	int ndx;
+
+	ndx = m_CbnUpdateFrequency.AddString(_T("always"));
+	m_CbnUpdateFrequency.SetItemData(ndx, 0);
+	if(periodDays >= 30)
+	{
+		m_CbnUpdateFrequency.SetCurSel(ndx);
+	}
+
+	ndx = m_CbnUpdateFrequency.AddString(_T("daily"));
+	m_CbnUpdateFrequency.SetItemData(ndx, 1);
+	if(periodDays >= 30)
+	{
+		m_CbnUpdateFrequency.SetCurSel(ndx);
+	}
+
+	ndx = m_CbnUpdateFrequency.AddString(_T("weekly"));
+	m_CbnUpdateFrequency.SetItemData(ndx, 7);
+	if(periodDays >= 7)
+	{
+		m_CbnUpdateFrequency.SetCurSel(ndx);
+	}
+
+	ndx = m_CbnUpdateFrequency.AddString(_T("monthly"));
+	m_CbnUpdateFrequency.SetItemData(ndx, 30);
+	if(periodDays >= 0)
+	{
+		m_CbnUpdateFrequency.SetCurSel(ndx);
+	}
+
+	ndx = m_CbnUpdateFrequency.AddString(_T("never"));
+	m_CbnUpdateFrequency.SetItemData(ndx, ~(DWORD_PTR)0);
+	if(periodDays < 0)
+	{		
+		m_CbnUpdateFrequency.SetCurSel(ndx);
+	}
+
 	CheckDlgButton(IDC_CHECK1, TrackerSettings::Instance().UpdateSendGUID ? BST_CHECKED : BST_UNCHECKED);
-	SetDlgItemText(IDC_EDIT1, mpt::ToCString(TrackerSettings::Instance().UpdateUpdateURL.Get()));
 
+	GetDlgItem(IDC_STATIC_UPDATEPRIVACYTEXT)->SetWindowText(_T("A randomized user ID is created and transmitted alongside. This ID can only be linked to you or your computer by this very ID, which is stored solely on your computer. OpenMPT will use this information to gather usage statistics and to plan removal of support for older systems. The following information will be sent:"));
+
+	UpdateStatistics();
+
+	EnableDisableDialog();
+
 	m_SettingChangedNotifyGuard.Register(this);
 	SettingChanged(TrackerSettings::Instance().UpdateLastUpdateCheck.GetPath());
 
@@ -396,6 +596,43 @@
 }
 
 
+void CUpdateSetupDlg::UpdateStatistics()
+{
+	CUpdateCheck::Settings settings;
+
+	int updateChannel = TrackerSettings::Instance().UpdateChannel;
+	if(IsDlgButtonChecked(IDC_RADIO1)) updateChannel = UpdateChannelRelease;
+	if(IsDlgButtonChecked(IDC_RADIO2)) updateChannel = UpdateChannelNext;
+	if(IsDlgButtonChecked(IDC_RADIO3)) updateChannel = UpdateChannelDevelopment;
+
+	int updateCheckPeriod = (m_CbnUpdateFrequency.GetItemData(m_CbnUpdateFrequency.GetCurSel()) == ~(DWORD_PTR)0) ? -1 : static_cast<int>(m_CbnUpdateFrequency.GetItemData(m_CbnUpdateFrequency.GetCurSel()));
+
+	CString updateURL;
+	GetDlgItemText(IDC_EDIT1, updateURL);
+
+	settings.periodDays = updateCheckPeriod;
+	settings.channel = updateChannel;
+	settings.sendStatistics = (IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED);
+
+	mpt::ustring statistics;
+	statistics += MPT_USTRING("GET ") + CUpdateCheck::GetUpdateURLV2(settings) + MPT_ULITERAL("\n");
+	statistics += MPT_ULITERAL("\n");
+	if(settings.sendStatistics)
+	{
+		if(settings.statisticsUUID.IsValid())
+		{
+			statistics += MPT_USTRING("PUT ") + settings.apiURL + mpt::format(MPT_USTRING("statistics/%1"))(settings.statisticsUUID) + MPT_ULITERAL("\n");
+		} else
+		{
+			statistics += MPT_USTRING("POST ") + settings.apiURL + MPT_USTRING("statistics/") + MPT_ULITERAL("\n");
+		}
+		statistics += mpt::String::Replace(mpt::ToUnicode(mpt::CharsetUTF8, CUpdateCheck::GetStatisticsDataV3(settings)), MPT_USTRING("\t"), MPT_USTRING("    "));
+		statistics += MPT_ULITERAL("\n");
+	}
+	SetDlgItemText(IDC_EDIT_STATISTICS, mpt::ToCString(mpt::String::Replace(statistics, MPT_USTRING("\n"), MPT_USTRING("\r\n"))));
+}
+
+
 void CUpdateSetupDlg::SettingChanged(const SettingPath &changedPath)
 {
 	if(changedPath == TrackerSettings::Instance().UpdateLastUpdateCheck.GetPath())
@@ -420,19 +657,42 @@
 }
 
 
+void CUpdateSetupDlg::EnableDisableDialog()
+{
+
+	/*
+	BOOL status = ((IsDlgButtonChecked(IDC_CHECK_UPDATEENABLED) != BST_UNCHECKED) ? TRUE : FALSE);
+	*/
+
+	GetDlgItem(IDC_CHECK_UPDATEENABLED)->EnableWindow(FALSE);
+	GetDlgItem(IDC_RADIO2)->EnableWindow(FALSE);
+
+}
+
+
+void CUpdateSetupDlg::OnSettingsChanged()
+{
+	EnableDisableDialog();
+	UpdateStatistics();
+	SetModified(TRUE);
+}
+
+
 void CUpdateSetupDlg::OnOK()
 {
-	int updateCheckPeriod = TrackerSettings::Instance().UpdateUpdateCheckPeriod;
-	if(IsDlgButtonChecked(IDC_RADIO1)) updateCheckPeriod = 0;
-	if(IsDlgButtonChecked(IDC_RADIO2)) updateCheckPeriod = 1;
-	if(IsDlgButtonChecked(IDC_RADIO3)) updateCheckPeriod = 7;
-	if(IsDlgButtonChecked(IDC_RADIO4)) updateCheckPeriod = 31;
+	int updateChannel = TrackerSettings::Instance().UpdateChannel;
+	if(IsDlgButtonChecked(IDC_RADIO1)) updateChannel = UpdateChannelRelease;
+	if(IsDlgButtonChecked(IDC_RADIO2)) updateChannel = UpdateChannelNext;
+	if(IsDlgButtonChecked(IDC_RADIO3)) updateChannel = UpdateChannelDevelopment;
 
+	int updateCheckPeriod = (m_CbnUpdateFrequency.GetItemData(m_CbnUpdateFrequency.GetCurSel()) == ~(DWORD_PTR)0) ? static_cast<int>(m_CbnUpdateFrequency.GetItemData(m_CbnUpdateFrequency.GetCurSel())) : -1;
+	
 	CString updateURL;
 	GetDlgItemText(IDC_EDIT1, updateURL);
 
-	TrackerSettings::Instance().UpdateUpdateCheckPeriod = updateCheckPeriod;
-	TrackerSettings::Instance().UpdateUpdateURL = mpt::ToUnicode(updateURL);
+	TrackerSettings::Instance().UpdateEnabled = (IsDlgButtonChecked(IDC_CHECK_UPDATEENABLED) != BST_UNCHECKED);
+	TrackerSettings::Instance().UpdateIntervalDays = updateCheckPeriod;
+	TrackerSettings::Instance().UpdateChannel = updateChannel;
 	TrackerSettings::Instance().UpdateSendGUID = (IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED);
 	
 	CPropertyPage::OnOK();
@@ -452,10 +712,4 @@
 }
 
 
-void CUpdateSetupDlg::OnResetURL()
-{
-	SetDlgItemText(IDC_EDIT1, mpt::ToCString(CUpdateCheck::GetDefaultUpdateURL()));
-}
-
-
 OPENMPT_NAMESPACE_END
Index: mptrack/UpdateCheck.h
===================================================================
--- mptrack/UpdateCheck.h	(revision 10676)
+++ mptrack/UpdateCheck.h	(working copy)
@@ -24,6 +24,13 @@
 OPENMPT_NAMESPACE_BEGIN
 
 
+enum UpdateChannel
+{
+	UpdateChannelRelease = 1,
+	UpdateChannelNext = 2,
+	UpdateChannelDevelopment = 3,
+};
+
 class CUpdateCheck
 {
 
@@ -33,7 +40,11 @@
 
 public:
 
-	static mpt::ustring GetDefaultUpdateURL();
+	static mpt::ustring GetDefaultChannelReleaseURL();
+	static mpt::ustring GetDefaultChannelNextURL();
+	static mpt::ustring GetDefaultChannelDevelopmentURL();
+
+	static mpt::ustring GetDefaultAPIURL();
 	
 	int32 GetNumCurrentRunningInstances();
 
@@ -42,7 +53,7 @@
 
 public:
 
-	struct Settings
+	struct Context
 	{
 		CWnd *window;
 		UINT msgProgress;
@@ -49,10 +60,20 @@
 		UINT msgSuccess;
 		UINT msgFailure;
 		bool autoUpdate;
-		mpt::ustring updateBaseURL;  // URL where the version check should be made.
+	};
+
+	struct Settings
+	{
+		int32 periodDays;
+		uint32 channel;
+		mpt::ustring channelReleaseURL;
+		mpt::ustring channelNextURL;
+		mpt::ustring channelDevelopmentURL;
+		mpt::ustring apiURL;
 		bool sendStatistics;
 		mpt::UUID statisticsUUID;
 		bool suggestDifferentBuilds;
+		Settings();
 	};
 
 	class Error
@@ -86,6 +107,14 @@
 	static void ShowSuccessGUI(WPARAM wparam, LPARAM lparam);
 	static void ShowFailureGUI(WPARAM wparam, LPARAM lparam);
 
+public:
+
+	// v2
+	static mpt::ustring GetUpdateURLV2(const Settings &settings);
+
+	// v3
+	static std::string GetStatisticsDataV3(const Settings &settings);  // UTF8
+
 protected:
 
 	static void StartUpdateCheckAsync(bool autoUpdate);
@@ -93,11 +122,12 @@
 	struct ThreadFunc
 	{
 		CUpdateCheck::Settings settings;
-		ThreadFunc(const CUpdateCheck::Settings &settings);
+		CUpdateCheck::Context context;
+		ThreadFunc(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context);
 		void operator () ();
 	};
 
-	static void CheckForUpdate(const CUpdateCheck::Settings &settings);
+	static void CheckForUpdate(const CUpdateCheck::Settings &settings, const CUpdateCheck::Context &context);
 
 	static CUpdateCheck::Result SearchUpdate(const CUpdateCheck::Settings &settings); // may throw
 	
@@ -111,17 +141,20 @@
 	CUpdateSetupDlg();
 
 protected:
+	virtual void DoDataExchange(CDataExchange *pDX);
 	virtual BOOL OnInitDialog();
 	virtual void OnOK();
 	virtual BOOL OnSetActive();
-	afx_msg void OnSettingsChanged() { SetModified(TRUE); }
+	afx_msg void OnSettingsChanged();
 	afx_msg void OnCheckNow();
-	afx_msg void OnResetURL();
 	virtual void SettingChanged(const SettingPath &changedPath);
+	void EnableDisableDialog();
+	void UpdateStatistics();
 	DECLARE_MESSAGE_MAP()
 
 private:
 	SettingChangedNotifyGuard m_SettingChangedNotifyGuard;
+	CComboBox m_CbnUpdateFrequency;
 };
 
 
Index: mptrack/WelcomeDialog.cpp
===================================================================
--- mptrack/WelcomeDialog.cpp	(revision 10676)
+++ mptrack/WelcomeDialog.cpp	(working copy)
@@ -147,7 +147,7 @@
 	CDialog::OnOK();
 
 	bool runUpdates = IsDlgButtonChecked(IDC_CHECK1) != BST_UNCHECKED;
-	TrackerSettings::Instance().UpdateUpdateCheckPeriod = (runUpdates ? 7 : 0);
+	TrackerSettings::Instance().UpdateIntervalDays = (runUpdates ? 7 : 0);
 	if(IsDlgButtonChecked(IDC_CHECK2) != BST_UNCHECKED)
 	{
 		FontSetting font = TrackerSettings::Instance().patternFont;
