A few more conversion changes:
A few more conversion changes:

* To go along with the previous change, XSFConfig now had internal get/set value functions that use type traits to choose between the enum and non-enum versions. (This also means I do not need to manually double-cast enums now.)
* Fix bug with the Windows API string conversions, so they do not include the trailing null byte in the output string.
* Added function to trim trailing 0s (and the decimal point if necessary) from a string that came from a double.

--- a/src/in_ncsf/XSFConfig_NCSF.cpp
+++ b/src/in_ncsf/XSFConfig_NCSF.cpp
@@ -290,7 +290,7 @@
 				if (chanId < 8)
 					buf += L"PSG/Noise?";
 				else if (chanId < 14)
-					buf += std::to_wstring(chn.reg.waveDuty == 7 ? 0 : 12.5 * (chn.reg.waveDuty + 1)) + L"% Square";
+					buf += ConvertFuncs::TrimDoubleString(std::to_wstring(chn.reg.waveDuty == 7 ? 0 : 12.5 * (chn.reg.waveDuty + 1))) + L"% Square";
 				else
 					buf += L"Noise";
 				buf += L")";
@@ -304,7 +304,7 @@
 
 			std::wstring tmpBuf = ConvertFuncs::StringToWString(NumToHexString(chn.reg.timer)).substr(2);
 			buf = L"$" + tmpBuf + L" (";
-			tmpBuf = std::to_wstring((ARM7_CLOCK / 2) / static_cast<double>(0x10000 - chn.reg.timer) / 8);
+			tmpBuf = ConvertFuncs::TrimDoubleString(std::to_wstring((ARM7_CLOCK / 2) / static_cast<double>(0x10000 - chn.reg.timer) / 8));
 			if (tmpBuf.find('.') != std::wstring::npos)
 				tmpBuf = tmpBuf.substr(0, tmpBuf.find('.') + 2);
 			buf += tmpBuf + L" Hz)";

--- a/src/in_xsf_framework/XSFConfig.cpp
+++ b/src/in_xsf_framework/XSFConfig.cpp
@@ -68,8 +68,8 @@
 	this->defaultLength = ConvertFuncs::StringToMS(this->configIO->GetValue("DefaultLength", XSFConfig::initDefaultLength));
 	this->defaultFade = ConvertFuncs::StringToMS(this->configIO->GetValue("DefaultFade", XSFConfig::initDefaultFade));
 	this->volume = this->configIO->GetValue("Volume", XSFConfig::initVolume);
-	this->volumeType = static_cast<VolumeType>(this->configIO->GetValue("VolumeType", static_cast<int>(XSFConfig::initVolumeType)));
-	this->peakType = static_cast<PeakType>(this->configIO->GetValue("PeakType", static_cast<int>(XSFConfig::initPeakType)));
+	this->volumeType = this->configIO->GetValue("VolumeType", XSFConfig::initVolumeType);
+	this->peakType = this->configIO->GetValue("PeakType", XSFConfig::initPeakType);
 	this->sampleRate = this->configIO->GetValue("SampleRate", XSFConfig::initSampleRate);
 	this->titleFormat = this->configIO->GetValue("TitleFormat", XSFConfig::initTitleFormat);
 
@@ -228,7 +228,7 @@
 			SetWindowTextW(GetDlgItem(hwndDlg, idDefaultFade), ConvertFuncs::MSToWString(this->defaultFade).c_str());
 			SetWindowTextW(GetDlgItem(hwndDlg, idSkipSilenceOnStartSec), ConvertFuncs::MSToWString(this->skipSilenceOnStartSec).c_str());
 			SetWindowTextW(GetDlgItem(hwndDlg, idDetectSilenceSec), ConvertFuncs::MSToWString(this->detectSilenceSec).c_str());
-			SetWindowTextW(GetDlgItem(hwndDlg, idVolume), std::to_wstring(this->volume).c_str());
+			SetWindowTextW(GetDlgItem(hwndDlg, idVolume), ConvertFuncs::TrimDoubleString(std::to_wstring(this->volume)).c_str());
 			SendMessageW(GetDlgItem(hwndDlg, idReplayGain), CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(L"Disabled"));
 			SendMessageW(GetDlgItem(hwndDlg, idReplayGain), CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(L"Use Volume Tag"));
 			SendMessageW(GetDlgItem(hwndDlg, idReplayGain), CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(L"Track"));
@@ -335,7 +335,7 @@
 	SetWindowTextW(GetDlgItem(hwndDlg, idDefaultFade), ConvertFuncs::StringToWString(XSFConfig::initDefaultFade).c_str());
 	SetWindowTextW(GetDlgItem(hwndDlg, idSkipSilenceOnStartSec), ConvertFuncs::StringToWString(XSFConfig::initSkipSilenceOnStartSec).c_str());
 	SetWindowTextW(GetDlgItem(hwndDlg, idDetectSilenceSec), ConvertFuncs::StringToWString(XSFConfig::initDetectSilenceSec).c_str());
-	SetWindowTextW(GetDlgItem(hwndDlg, idVolume), std::to_wstring(XSFConfig::initVolume).c_str());
+	SetWindowTextW(GetDlgItem(hwndDlg, idVolume), ConvertFuncs::TrimDoubleString(std::to_wstring(XSFConfig::initVolume)).c_str());
 	SendMessageW(GetDlgItem(hwndDlg, idReplayGain), CB_SETCURSEL, XSFConfig::initVolumeType, 0);
 	SendMessageW(GetDlgItem(hwndDlg, idClipProtect), CB_SETCURSEL, XSFConfig::initPeakType, 0);
 	auto found = std::find(this->supportedSampleRates.begin(), this->supportedSampleRates.end(), XSFConfig::initSampleRate);

--- a/src/in_xsf_framework/XSFConfig.h
+++ b/src/in_xsf_framework/XSFConfig.h
@@ -16,6 +16,26 @@
 
 class XSFConfigIO
 {
+private:
+	// enum versions
+	template<typename T> typename std::enable_if_t<std::is_enum_v<T>, T> GetValueInternal(const std::string &name, const T &defaultValue) const
+	{
+		return convertTo<T>(this->GetValueString(name, std::to_string(static_cast<std::underlying_type_t<T>>(defaultValue))));
+	}
+	template<typename T> typename std::enable_if_t<std::is_enum_v<T>> SetValueInternal(const std::string &name, const T &value)
+	{
+		this->SetValueString(name, std::to_string(static_cast<std::underlying_type_t<T>>(value)));
+	}
+
+	// non-enum versions
+	template<typename T> typename std::enable_if_t<!std::is_enum_v<T> &&std::is_arithmetic_v<T>, T> GetValueInternal(const std::string &name, const T &defaultValue) const
+	{
+		return convertTo<T>(this->GetValueString(name, std::to_string(defaultValue)));
+	}
+	template<typename T> typename std::enable_if_t<!std::is_enum_v<T> &&std::is_arithmetic_v<T>> SetValueInternal(const std::string &name, const T &value)
+	{
+		this->SetValueString(name, std::to_string(value));
+	}
 protected:
 	XSFConfigIO() { }
 public:
@@ -24,10 +44,10 @@
 
 	virtual ~XSFConfigIO() { }
 	virtual void SetValueString(const std::string &name, const std::string &value) = 0;
-	template<typename T> void SetValue(const std::string &name, const T &value) { this->SetValueString(name, std::to_string(value)); }
+	template<typename T> void SetValue(const std::string &name, const T &value) { this->SetValueInternal(name, value); }
 	void SetValue(const std::string &name, const std::string &value) { this->SetValueString(name, value); }
 	virtual std::string GetValueString(const std::string &name, const std::string &defaultValue) const = 0;
-	template<typename T> T GetValue(const std::string &name, const T &defaultValue) const { return convertTo<T>(this->GetValueString(name, std::to_string(defaultValue))); }
+	template<typename T> T GetValue(const std::string &name, const T &defaultValue) const { return this->GetValueInternal(name, defaultValue); }
 	std::string GetValue(const std::string &name, const std::string &defaultValue) const { return this->GetValueString(name, defaultValue); }
 	virtual void SetHInstance(HINSTANCE) { }
 	virtual HINSTANCE GetHInstance() const { return nullptr; }

--- a/src/in_xsf_framework/convert.h
+++ b/src/in_xsf_framework/convert.h
@@ -130,19 +130,28 @@
 	{
 		double seconds = time / 1000.0;
 		if (seconds < 60)
-			return std::to_string(seconds);
+			return ConvertFuncs::TrimDoubleString(std::to_string(seconds));
 		unsigned long minutes = static_cast<unsigned long>(seconds) / 60;
 		seconds -= minutes * 60;
 		if (minutes < 60)
-			return std::to_string(minutes) + ":" + (seconds < 10 ? "0" : "") + std::to_string(seconds);
+			return std::to_string(minutes) + ":" + (seconds < 10 ? "0" : "") + ConvertFuncs::TrimDoubleString(std::to_string(seconds));
 		unsigned long hours = minutes / 60;
 		minutes %= 60;
-		return std::to_string(hours) + ":" + (minutes < 10 ? "0" : "") + std::to_string(minutes) + ":" + (seconds < 10 ? "0" : "") + std::to_string(seconds);
+		return std::to_string(hours) + ":" + (minutes < 10 ? "0" : "") + std::to_string(minutes) + ":" + (seconds < 10 ? "0" : "") + ConvertFuncs::TrimDoubleString(std::to_string(seconds));
 	}
 
 	static std::wstring MSToWString(unsigned long time)
 	{
 		return ConvertFuncs::StringToWString(ConvertFuncs::MSToString(time));
+	}
+
+	// Derived from https://stackoverflow.com/a/13709929 (specifically the comments)
+	template<typename T> static std::basic_string<T> TrimDoubleString(const std::basic_string<T> &str)
+	{
+		auto strCopy = str;
+		auto lastNonZero = strCopy.find_last_not_of('0');
+		strCopy.erase(lastNonZero + (lastNonZero == strCopy.find('.') ? 0 : 1));
+		return strCopy;
 	}
 
 	static std::wstring StringToWString(const std::string &str)
@@ -151,7 +160,7 @@
 		int bufferSize = MultiByteToWideChar(CP_UTF8, 0, strC, -1, nullptr, 0);
 		auto buffer = std::vector<wchar_t>(bufferSize);
 		MultiByteToWideChar(CP_UTF8, 0, strC, -1, &buffer[0], bufferSize);
-		return std::wstring(buffer.begin(), buffer.end());
+		return std::wstring(buffer.begin(), buffer.begin() + bufferSize - 1);
 	}
 
 	static std::string WStringToString(const std::wstring &wstr)
@@ -160,7 +169,7 @@
 		int bufferSize = WideCharToMultiByte(CP_UTF8, 0, wstrC, -1, nullptr, 0, nullptr, nullptr);
 		auto buffer = std::vector<char>(bufferSize);
 		WideCharToMultiByte(CP_UTF8, 0, wstrC, -1, &buffer[0], bufferSize, nullptr, nullptr);
-		return std::string(buffer.begin(), buffer.end());
+		return std::string(buffer.begin(), buffer.begin() + bufferSize - 1);
 	}
 };