Some template cleanup:
Some template cleanup:

* Move the ToIntegral function from NCSF specifically to the framework in general, so it can be used in the base framework's code too.
* Use type traits a bit more.
* Move convertTo into the ConvertFuncs class.

--- a/src/in_ncsf/SSEQPlayer/Channel.cpp
+++ b/src/in_ncsf/SSEQPlayer/Channel.cpp
@@ -23,6 +23,7 @@
 #include "XSFCommon.h"
 #include "common.h"
 #include "consts.h"
+#include "convert.h"
 
 NDSSoundRegister::NDSSoundRegister() : volumeMul(0), volumeDiv(0), panning(0), waveDuty(0), repeatMode(0), format(0), enable(false),
 	source(nullptr), timer(0), psgX(0), psgLast(0), psgLastCount(0), samplePosition(0), sampleIncrease(0), loopStart(0), length(0), totalLength(0)
@@ -123,7 +124,7 @@
 	this->manualSweep = false;
 	this->sweepPitch = trk.sweepPitch;
 	this->sweepCnt = 0;
-	if (!trk.state[ToIntegral(TrackState::PortamentoBit)])
+	if (!trk.state[ConvertFuncs::ToIntegral(TrackState::PortamentoBit)])
 	{
 		this->sweepLen = 0;
 		return;
@@ -192,7 +193,7 @@
 	if (trackFlags.none())
 		return;
 
-	if (trackFlags[ToIntegral(TrackUpdateFlag::Length)])
+	if (trackFlags[ConvertFuncs::ToIntegral(TrackUpdateFlag::Length)])
 	{
 		ChannelState st = this->state;
 		if (st > ChannelState::Start)
@@ -203,30 +204,30 @@
 				++this->sweepCnt;
 		}
 	}
-	if (trackFlags[ToIntegral(TrackUpdateFlag::Volume)])
+	if (trackFlags[ConvertFuncs::ToIntegral(TrackUpdateFlag::Volume)])
 	{
 		this->UpdateVol(trk);
-		this->flags.set(ToIntegral(ChannelFlag::UpdateVolume));
-	}
-	if (trackFlags[ToIntegral(TrackUpdateFlag::Pan)])
+		this->flags.set(ConvertFuncs::ToIntegral(ChannelFlag::UpdateVolume));
+	}
+	if (trackFlags[ConvertFuncs::ToIntegral(TrackUpdateFlag::Pan)])
 	{
 		this->UpdatePan(trk);
-		this->flags.set(ToIntegral(ChannelFlag::UpdatePan));
-	}
-	if (trackFlags[ToIntegral(TrackUpdateFlag::Timer)])
+		this->flags.set(ConvertFuncs::ToIntegral(ChannelFlag::UpdatePan));
+	}
+	if (trackFlags[ConvertFuncs::ToIntegral(TrackUpdateFlag::Timer)])
 	{
 		this->UpdateTune(trk);
-		this->flags.set(ToIntegral(ChannelFlag::UpdateTimer));
-	}
-	if (trackFlags[ToIntegral(TrackUpdateFlag::Modulation)])
+		this->flags.set(ConvertFuncs::ToIntegral(ChannelFlag::UpdateTimer));
+	}
+	if (trackFlags[ConvertFuncs::ToIntegral(TrackUpdateFlag::Modulation)])
 	{
 		int oldType = this->modType;
 		int newType = trk.modType;
 		this->UpdateMod(trk);
 		if (oldType != newType)
 		{
-			this->flags.set(ToIntegral(getModFlag(oldType)));
-			this->flags.set(ToIntegral(getModFlag(newType)));
+			this->flags.set(ConvertFuncs::ToIntegral(getModFlag(oldType)));
+			this->flags.set(ConvertFuncs::ToIntegral(getModFlag(newType)));
 		}
 	}
 }
@@ -444,9 +445,9 @@
 	bool bInStart = this->state == ChannelState::Start;
 	bool bPitchSweep = this->sweepPitch && this->sweepLen && this->sweepCnt <= this->sweepLen;
 	bool bModulation = !!this->modDepth;
-	bool bVolNeedUpdate = this->flags[ToIntegral(ChannelFlag::UpdateVolume)] || bNotInSustain;
-	bool bPanNeedUpdate = this->flags[ToIntegral(ChannelFlag::UpdatePan)] || bInStart;
-	bool bTmrNeedUpdate = this->flags[ToIntegral(ChannelFlag::UpdateTimer)] || bInStart || bPitchSweep;
+	bool bVolNeedUpdate = this->flags[ConvertFuncs::ToIntegral(ChannelFlag::UpdateVolume)] || bNotInSustain;
+	bool bPanNeedUpdate = this->flags[ConvertFuncs::ToIntegral(ChannelFlag::UpdatePan)] || bInStart;
+	bool bTmrNeedUpdate = this->flags[ConvertFuncs::ToIntegral(ChannelFlag::UpdateTimer)] || bInStart || bPitchSweep;
 	int modParam = 0;
 
 	switch (this->state)
@@ -548,7 +549,7 @@
 			tmr = Timer_Adjust(tmr, totalAdj);
 		this->reg.timer = -tmr;
 		this->reg.sampleIncrease = (ARM7_CLOCK / static_cast<double>(this->ply->sampleRate * 2)) / (0x10000 - this->reg.timer);
-		this->flags.reset(ToIntegral(ChannelFlag::UpdateTimer));
+		this->flags.reset(ConvertFuncs::ToIntegral(ChannelFlag::UpdateTimer));
 	}
 
 	if (bVolNeedUpdate || bPanNeedUpdate)
@@ -576,7 +577,7 @@
 
 			this->vol = static_cast<std::uint16_t>(((cr & SOUND_VOL(0x7F)) << 4) >> calcVolDivShift((cr & SOUND_VOLDIV(3)) >> 8));
 
-			this->flags.reset(ToIntegral(ChannelFlag::UpdateVolume));
+			this->flags.reset(ConvertFuncs::ToIntegral(ChannelFlag::UpdateVolume));
 		}
 
 		if (bPanNeedUpdate)
@@ -590,7 +591,7 @@
 
 			cr &= ~SOUND_PAN(0x7F);
 			cr |= SOUND_PAN(realPan);
-			this->flags.reset(ToIntegral(ChannelFlag::UpdatePan));
+			this->flags.reset(ConvertFuncs::ToIntegral(ChannelFlag::UpdatePan));
 		}
 
 		this->tempReg.CR = cr;

--- a/src/in_ncsf/SSEQPlayer/Channel.h
+++ b/src/in_ncsf/SSEQPlayer/Channel.h
@@ -18,6 +18,7 @@
 #include <cstdint>
 #include "common.h"
 #include "consts.h"
+#include "convert.h"
 
 struct SWAV;
 struct Track;
@@ -179,7 +180,7 @@
 	std::uint8_t prio;
 	bool manualSweep;
 
-	std::bitset<ToIntegral(ChannelFlag::Bits)> flags;
+	std::bitset<ConvertFuncs::ToIntegral(ChannelFlag::Bits)> flags;
 	std::int8_t pan; // -64 .. 63
 	std::int16_t extAmpl;
 

--- a/src/in_ncsf/SSEQPlayer/INFOSection.h
+++ b/src/in_ncsf/SSEQPlayer/INFOSection.h
@@ -9,6 +9,7 @@
 #pragma once
 
 #include <map>
+#include <type_traits>
 #include <cstdint>
 #include "INFOEntry.h"
 
@@ -16,7 +17,7 @@
 
 template<typename T> struct INFORecord
 {
-	std::map<std::uint32_t, T> entries;
+	std::enable_if_t<std::is_base_of_v<INFOEntry, T>, std::map<std::uint32_t, T>> entries;
 
 	INFORecord();
 

--- a/src/in_ncsf/SSEQPlayer/Player.cpp
+++ b/src/in_ncsf/SSEQPlayer/Player.cpp
@@ -13,6 +13,7 @@
 #include "SSEQ.h"
 #include "common.h"
 #include "consts.h"
+#include "convert.h"
 
 Player::Player() : prio(0), nTracks(0), tempo(0), tempoCount(0), tempoRate(0), masterVol(0), sseqVol(0), sseq(nullptr), allowedChannels(0), sampleRate(0),
 	interpolation(Interpolation::None)
@@ -96,8 +97,8 @@
 	static const std::uint8_t arraySizes[] = { sizeof(pcmChnArray), sizeof(psgChnArray), sizeof(noiseChnArray) };
 	static const std::uint8_t *const arrayArray[] = { pcmChnArray, psgChnArray, noiseChnArray };
 
-	auto chnArray = arrayArray[ToIntegral(type)];
-	int arraySize = arraySizes[ToIntegral(type)];
+	auto chnArray = arrayArray[ConvertFuncs::ToIntegral(type)];
+	int arraySize = arraySizes[ConvertFuncs::ToIntegral(type)];
 
 	int curChnNo = -1;
 	for (int i = 0; i < arraySize; ++i)
@@ -133,10 +134,10 @@
 	for (int i = 0; i < FSS_MAXTRACKS; ++i)
 	{
 		Track &thisTrk = this->tracks[i];
-		if (!thisTrk.state[ToIntegral(TrackState::AllocateBit)])
+		if (!thisTrk.state[ConvertFuncs::ToIntegral(TrackState::AllocateBit)])
 		{
 			thisTrk.Zero();
-			thisTrk.state.set(ToIntegral(TrackState::AllocateBit));
+			thisTrk.state.set(ConvertFuncs::ToIntegral(TrackState::AllocateBit));
 			thisTrk.updateFlags.reset();
 			return i;
 		}

--- a/src/in_ncsf/SSEQPlayer/Track.cpp
+++ b/src/in_ncsf/SSEQPlayer/Track.cpp
@@ -19,6 +19,7 @@
 #include "Track.h"
 #include "common.h"
 #include "consts.h"
+#include "convert.h"
 
 Track::Track()
 {
@@ -71,8 +72,8 @@
 void Track::ClearState()
 {
 	this->state.reset();
-	this->state.set(ToIntegral(TrackState::AllocateBit));
-	this->state.set(ToIntegral(TrackState::NoteWait));
+	this->state.set(ConvertFuncs::ToIntegral(TrackState::AllocateBit));
+	this->state.set(ConvertFuncs::ToIntegral(TrackState::NoteWait));
 	this->prio = this->ply->prio + 64;
 
 	this->pos = this->startPos;
@@ -253,7 +254,7 @@
 	chn->UpdatePorta(*this);
 
 	this->portaKey = key;
-	chn->flags.set(ToIntegral(ChannelFlag::UpdateTimer));
+	chn->flags.set(ConvertFuncs::ToIntegral(ChannelFlag::UpdateTimer));
 
 	return i;
 }
@@ -493,10 +494,10 @@
 void Track::Run()
 {
 	// Indicate "heartbeat" for this track
-	this->updateFlags.set(ToIntegral(TrackUpdateFlag::Length));
+	this->updateFlags.set(ConvertFuncs::ToIntegral(TrackUpdateFlag::Length));
 
 	// Exit if the track has already ended
-	if (this->state[ToIntegral(TrackState::End)])
+	if (this->state[ConvertFuncs::ToIntegral(TrackState::End)])
 		return;
 
 	if (this->wait)
@@ -521,9 +522,9 @@
 			std::uint8_t key = static_cast<std::uint8_t>(cmd + this->transpose);
 			int vel = this->overriding.val<std::uint8_t>(pData, read8, true);
 			int len = this->overriding.val<int>(pData, readvl);
-			if (this->state[ToIntegral(TrackState::NoteWait)])
+			if (this->state[ConvertFuncs::ToIntegral(TrackState::NoteWait)])
 				this->wait = len;
-			if (this->state[ToIntegral(TrackState::TieBit)])
+			if (this->state[ConvertFuncs::ToIntegral(TrackState::TieBit)])
 				this->NoteOnTie(key, vel);
 			else
 				this->NoteOn(key, vel, len);
@@ -579,18 +580,18 @@
 
 				case SSEQCommand::Pan:
 					this->pan = this->overriding.val<std::uint8_t>(pData, read8) - 64;
-					this->updateFlags.set(ToIntegral(TrackUpdateFlag::Pan));
+					this->updateFlags.set(ConvertFuncs::ToIntegral(TrackUpdateFlag::Pan));
 					break;
 
 				case SSEQCommand::Volume:
 					this->vol = this->overriding.val<std::uint8_t>(pData, read8);
-					this->updateFlags.set(ToIntegral(TrackUpdateFlag::Volume));
+					this->updateFlags.set(ConvertFuncs::ToIntegral(TrackUpdateFlag::Volume));
 					break;
 
 				case SSEQCommand::MasterVolume:
 					this->ply->masterVol = Cnv_Sust(this->overriding.val<std::uint8_t>(pData, read8));
 					for (std::uint8_t i = 0; i < this->ply->nTracks; ++i)
-						this->ply->tracks[this->ply->trackIds[i]].updateFlags.set(ToIntegral(TrackUpdateFlag::Volume));
+						this->ply->tracks[this->ply->trackIds[i]].updateFlags.set(ConvertFuncs::ToIntegral(TrackUpdateFlag::Volume));
 					break;
 
 				case SSEQCommand::Priority:
@@ -599,17 +600,17 @@
 					break;
 
 				case SSEQCommand::NoteWait:
-					this->state.set(ToIntegral(TrackState::NoteWait), !!read8(pData));
+					this->state.set(ConvertFuncs::ToIntegral(TrackState::NoteWait), !!read8(pData));
 					break;
 
 				case SSEQCommand::Tie:
-					this->state.set(ToIntegral(TrackState::TieBit), !!read8(pData));
+					this->state.set(ConvertFuncs::ToIntegral(TrackState::TieBit), !!read8(pData));
 					this->ReleaseAllNotes();
 					break;
 
 				case SSEQCommand::Expression:
 					this->expr = this->overriding.val<std::uint8_t>(pData, read8);
-					this->updateFlags.set(ToIntegral(TrackUpdateFlag::Volume));
+					this->updateFlags.set(ConvertFuncs::ToIntegral(TrackUpdateFlag::Volume));
 					break;
 
 				case SSEQCommand::Tempo:
@@ -617,7 +618,7 @@
 					break;
 
 				case SSEQCommand::End:
-					this->state.set(ToIntegral(TrackState::End));
+					this->state.set(ConvertFuncs::ToIntegral(TrackState::End));
 					return;
 
 				case SSEQCommand::LoopStart:
@@ -652,12 +653,12 @@
 
 				case SSEQCommand::PitchBend:
 					this->pitchBend = this->overriding.val<std::int8_t>(pData, read8);
-					this->updateFlags.set(ToIntegral(TrackUpdateFlag::Timer));
+					this->updateFlags.set(ConvertFuncs::ToIntegral(TrackUpdateFlag::Timer));
 					break;
 
 				case SSEQCommand::PitchBendRange:
 					this->pitchBendRange = read8(pData);
-					this->updateFlags.set(ToIntegral(TrackUpdateFlag::Timer));
+					this->updateFlags.set(ConvertFuncs::ToIntegral(TrackUpdateFlag::Timer));
 					break;
 
 				//-----------------------------------------------------------------
@@ -686,24 +687,24 @@
 
 				case SSEQCommand::PortamentoKey:
 					this->portaKey = static_cast<std::uint8_t>(read8(pData) + this->transpose);
-					this->state.set(ToIntegral(TrackState::PortamentoBit));
+					this->state.set(ConvertFuncs::ToIntegral(TrackState::PortamentoBit));
 					// Update here?
 					break;
 
 				case SSEQCommand::PortamentoFlag:
-					this->state.set(ToIntegral(TrackState::PortamentoBit), !!read8(pData));
+					this->state.set(ConvertFuncs::ToIntegral(TrackState::PortamentoBit), !!read8(pData));
 					// Update here?
 					break;
 
 				case SSEQCommand::PortamentoTime:
 					this->portaTime = this->overriding.val<std::uint8_t>(pData, read8);
-					this->state.set(ToIntegral(TrackState::PortamentoBit));
+					this->state.set(ConvertFuncs::ToIntegral(TrackState::PortamentoBit));
 					// Update here?
 					break;
 
 				case SSEQCommand::SweepPitch:
 					this->sweepPitch = this->overriding.val<std::int16_t>(pData, read16);
-					this->state.set(ToIntegral(TrackState::PortamentoBit));
+					this->state.set(ConvertFuncs::ToIntegral(TrackState::PortamentoBit));
 					// Update here?
 					break;
 
@@ -713,27 +714,27 @@
 
 				case SSEQCommand::ModulationDepth:
 					this->modDepth = this->overriding.val<std::uint8_t>(pData, read8);
-					this->updateFlags.set(ToIntegral(TrackUpdateFlag::Modulation));
+					this->updateFlags.set(ConvertFuncs::ToIntegral(TrackUpdateFlag::Modulation));
 					break;
 
 				case SSEQCommand::ModulationSpeed:
 					this->modSpeed = this->overriding.val<std::uint8_t>(pData, read8);
-					this->updateFlags.set(ToIntegral(TrackUpdateFlag::Modulation));
+					this->updateFlags.set(ConvertFuncs::ToIntegral(TrackUpdateFlag::Modulation));
 					break;
 
 				case SSEQCommand::ModulationType:
 					this->modType = read8(pData);
-					this->updateFlags.set(ToIntegral(TrackUpdateFlag::Modulation));
+					this->updateFlags.set(ConvertFuncs::ToIntegral(TrackUpdateFlag::Modulation));
 					break;
 
 				case SSEQCommand::ModulationRange:
 					this->modRange = read8(pData);
-					this->updateFlags.set(ToIntegral(TrackUpdateFlag::Modulation));
+					this->updateFlags.set(ConvertFuncs::ToIntegral(TrackUpdateFlag::Modulation));
 					break;
 
 				case SSEQCommand::ModulationDelay:
 					this->modDelay = this->overriding.val<std::uint16_t>(pData, read16);
-					this->updateFlags.set(ToIntegral(TrackUpdateFlag::Modulation));
+					this->updateFlags.set(ConvertFuncs::ToIntegral(TrackUpdateFlag::Modulation));
 					break;
 
 				//-----------------------------------------------------------------
@@ -744,7 +745,7 @@
 				{
 					this->overriding() = true;
 					this->overriding.cmd = read8(pData);
-					if ((this->overriding.cmd >= ToIntegral(SSEQCommand::SetVariable) && this->overriding.cmd <= ToIntegral(SSEQCommand::CompareNotEqualTo)) || this->overriding.cmd < 0x80)
+					if ((this->overriding.cmd >= ConvertFuncs::ToIntegral(SSEQCommand::SetVariable) && this->overriding.cmd <= ConvertFuncs::ToIntegral(SSEQCommand::CompareNotEqualTo)) || this->overriding.cmd < 0x80)
 						this->overriding.extraValue = read8(pData);
 					std::int16_t minVal = static_cast<std::int16_t>(read16(pData));
 					std::int16_t maxVal = static_cast<std::int16_t>(read16(pData));
@@ -759,7 +760,7 @@
 				case SSEQCommand::FromVariable:
 					this->overriding() = true;
 					this->overriding.cmd = read8(pData);
-					if ((this->overriding.cmd >= ToIntegral(SSEQCommand::SetVariable) && this->overriding.cmd <= ToIntegral(SSEQCommand::CompareNotEqualTo)) || this->overriding.cmd < 0x80)
+					if ((this->overriding.cmd >= ConvertFuncs::ToIntegral(SSEQCommand::SetVariable) && this->overriding.cmd <= ConvertFuncs::ToIntegral(SSEQCommand::CompareNotEqualTo)) || this->overriding.cmd < 0x80)
 						this->overriding.extraValue = read8(pData);
 					this->overriding.value = this->ply->variables[read8(pData)];
 					break;
@@ -774,7 +775,7 @@
 				{
 					std::int8_t varNo = this->overriding.val<std::int8_t>(pData, read8, true);
 					value = this->overriding.val<std::int16_t>(pData, read16);
-					if (cmd == ToIntegral(SSEQCommand::DivideVariable) && !value) // Division by 0, skip it to prevent crashing
+					if (cmd == ConvertFuncs::ToIntegral(SSEQCommand::DivideVariable) && !value) // Division by 0, skip it to prevent crashing
 						break;
 					this->ply->variables[varNo] = VarFunc(cmd)(this->ply->variables[varNo], static_cast<std::int16_t>(value));
 					break;
@@ -808,7 +809,7 @@
 						if (extraByte)
 						{
 							int extraCmd = read8(pData);
-							if ((extraCmd >= ToIntegral(SSEQCommand::SetVariable) && extraCmd <= ToIntegral(SSEQCommand::CompareNotEqualTo)) || extraCmd < 0x80)
+							if ((extraCmd >= ConvertFuncs::ToIntegral(SSEQCommand::SetVariable) && extraCmd <= ConvertFuncs::ToIntegral(SSEQCommand::CompareNotEqualTo)) || extraCmd < 0x80)
 								++cmdBytes;
 						}
 						*pData += cmdBytes;
@@ -822,7 +823,7 @@
 			}
 		}
 
-		if (cmd != ToIntegral(SSEQCommand::Random) && cmd != ToIntegral(SSEQCommand::FromVariable))
+		if (cmd != ConvertFuncs::ToIntegral(SSEQCommand::Random) && cmd != ConvertFuncs::ToIntegral(SSEQCommand::FromVariable))
 			this->overriding() = false;
 	}
 }

--- a/src/in_ncsf/SSEQPlayer/Track.h
+++ b/src/in_ncsf/SSEQPlayer/Track.h
@@ -14,6 +14,7 @@
 #include <cstdint>
 #include "common.h"
 #include "consts.h"
+#include "convert.h"
 
 struct Player;
 
@@ -55,7 +56,7 @@
 {
 	std::int8_t trackId;
 
-	std::bitset<ToIntegral(TrackState::Bits)> state;
+	std::bitset<ConvertFuncs::ToIntegral(TrackState::Bits)> state;
 	std::uint8_t num, prio;
 	Player *ply;
 
@@ -82,7 +83,7 @@
 	std::uint8_t modType, modSpeed, modDepth, modRange;
 	std::uint16_t modDelay;
 
-	std::bitset<ToIntegral(TrackUpdateFlag::Bits)> updateFlags;
+	std::bitset<ConvertFuncs::ToIntegral(TrackUpdateFlag::Bits)> updateFlags;
 
 	Track();
 

--- a/src/in_ncsf/SSEQPlayer/common.h
+++ b/src/in_ncsf/SSEQPlayer/common.h
@@ -29,7 +29,7 @@
 	{
 	}
 
-	template<typename T> T ReadLE()
+	template<typename T> typename std::enable_if_t<std::is_integral_v<T>, T> ReadLE()
 	{
 		T finalVal = 0;
 		for (std::size_t i = 0; i < sizeof(T); ++i)
@@ -37,7 +37,7 @@
 		return finalVal;
 	}
 
-	template<typename T, std::size_t N> void ReadLE(T (&arr)[N])
+	template<typename T, std::size_t N> typename std::enable_if_t<std::is_integral_v<T>> ReadLE(T (&arr)[N])
 	{
 		for (std::size_t i = 0; i < N; ++i)
 			arr[i] = this->ReadLE<T>();
@@ -49,7 +49,7 @@
 		this->pos += N;
 	}
 
-	template<typename T> void ReadLE(std::vector<T> &arr)
+	template<typename T> typename std::enable_if_t<std::is_integral_v<T>> ReadLE(std::vector<T> &arr)
 	{
 		for (std::size_t i = 0, len = arr.size(); i < len; ++i)
 			arr[i] = this->ReadLE<T>();
@@ -83,7 +83,7 @@
  * as little-endian formating.
  */
 
-template<typename T> inline T ReadLE(const std::uint8_t *arr)
+template<typename T> inline typename std::enable_if_t<std::is_integral_v<T>, T> ReadLE(const std::uint8_t *arr)
 {
 	T finalVal = 0;
 	for (std::size_t i = 0; i < sizeof(T); ++i)
@@ -97,7 +97,7 @@
  * integers are in the format of 0x00, 16-bit integers are in the format of
  * 0x0000, and so on.
  */
-template<typename T> inline std::string NumToHexString(const T &num)
+template<typename T> inline typename std::enable_if_t<std::is_integral_v<T>, std::string> NumToHexString(const T &num)
 {
 	std::string hex;
 	std::uint8_t len = sizeof(T) * 2;
@@ -122,12 +122,6 @@
 inline constexpr int REC_GROUP = 5;
 inline constexpr int REC_PLAYER2 = 6;
 inline constexpr int REC_STRM = 7;
-
-// Comes from https://stackoverflow.com/a/14589519
-template<typename T> inline constexpr auto ToIntegral(const T &e)
-{
-	return static_cast<std::underlying_type_t<T>>(e);
-}
 
 template<std::size_t N> inline bool VerifyHeader(std::int8_t (&arr)[N], const std::string &header)
 {

--- a/src/in_ncsf/XSFConfig_NCSF.cpp
+++ b/src/in_ncsf/XSFConfig_NCSF.cpp
@@ -312,7 +312,7 @@
 			}
 
 			static const std::wstring states[] = { L"NONE", L"START", L"ATTACK", L"DECAY", L"SUSTAIN", L"RELEASE" };
-			SetDlgItemTextW(hDlg, IDC_SOUND0STATE + chanId, states[ToIntegral(chn.state)].c_str());
+			SetDlgItemTextW(hDlg, IDC_SOUND0STATE + chanId, states[ConvertFuncs::ToIntegral(chn.state)].c_str());
 
 			SetDlgItemTextW(hDlg, IDC_SOUND0PNT + chanId, (L"samp #" + std::to_wstring(chn.reg.loopStart)).c_str());
 

--- a/src/in_xsf_framework/XSFCommon.h
+++ b/src/in_xsf_framework/XSFCommon.h
@@ -7,9 +7,10 @@
 
 #pragma once
 
+#include <fstream>
 #include <limits>
-#include <fstream>
 #include <string>
+#include <type_traits>
 #define _USE_MATH_DEFINES
 #include <cmath>
 #include <cstddef>
@@ -19,7 +20,7 @@
 #include "convert.h"
 
 // Code from http://learningcppisfun.blogspot.com/2010/04/comparing-floating-point-numbers.html
-template<typename T> inline bool fEqual(T x, T y, int N = 1)
+template<typename T> inline typename std::enable_if_t<std::is_floating_point_v<T>, bool> fEqual(T x, T y, int N = 1)
 {
 	T diff = std::abs(x - y);
 	T tolerance = N * std::numeric_limits<T>::epsilon();
@@ -40,7 +41,7 @@
 
 // Code from the following answer on Stack Overflow:
 // http://stackoverflow.com/a/15479212
-template<typename T> inline T NextHighestPowerOf2(T value)
+template<typename T> inline typename std::enable_if_t<std::is_integral_v<T>, T> NextHighestPowerOf2(T value)
 {
 	if (value < 1)
 		return 1;

--- a/src/in_xsf_framework/XSFConfig.cpp
+++ b/src/in_xsf_framework/XSFConfig.cpp
@@ -270,8 +270,8 @@
 	dialog->skipSilenceOnStart = ConvertFuncs::MSToString(this->skipSilenceOnStartSec);
 	dialog->detectSilence = ConvertFuncs::MSToString(this->detectSilenceSec);
 	dialog->volume = this->volume;
-	dialog->replayGain = static_cast<std::underlying_type_t<VolumeType>>(this->volumeType);
-	dialog->clipProtect = static_cast<std::underlying_type_t<PeakType>>(this->peakType);
+	dialog->replayGain = ConvertFuncs::ToIntegral(this->volumeType);
+	dialog->clipProtect = ConvertFuncs::ToIntegral(this->peakType);
 	auto found = std::find(this->supportedSampleRates.begin(), this->supportedSampleRates.end(), this->sampleRate);
 	dialog->sampleRate = found == this->supportedSampleRates.end() ? 0 : found - this->supportedSampleRates.begin();
 	dialog->titleFormat = this->titleFormat;
@@ -287,8 +287,8 @@
 	dialog->skipSilenceOnStart = XSFConfig::initSkipSilenceOnStartSec;
 	dialog->detectSilence = XSFConfig::initDetectSilenceSec;
 	dialog->volume = XSFConfig::initVolume;
-	dialog->replayGain = static_cast<std::underlying_type_t<VolumeType>>(XSFConfig::initVolumeType);
-	dialog->clipProtect = static_cast<std::underlying_type_t<PeakType>>(XSFConfig::initPeakType);
+	dialog->replayGain = ConvertFuncs::ToIntegral(XSFConfig::initVolumeType);
+	dialog->clipProtect = ConvertFuncs::ToIntegral(XSFConfig::initPeakType);
 	auto found = std::find(this->supportedSampleRates.begin(), this->supportedSampleRates.end(), XSFConfig::initSampleRate);
 	dialog->sampleRate = found == this->supportedSampleRates.end() ? 0 : found - this->supportedSampleRates.begin();
 	dialog->titleFormat = XSFConfig::initTitleFormat;

--- a/src/in_xsf_framework/XSFConfig.h
+++ b/src/in_xsf_framework/XSFConfig.h
@@ -26,17 +26,17 @@
 	// 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))));
+		return ConvertFuncs::To<T>(this->GetValueString(name, std::to_string(ConvertFuncs::ToIntegral(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)));
+		this->SetValueString(name, std::to_string(ConvertFuncs::ToIntegral(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)));
+		return ConvertFuncs::To<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)
 	{

--- a/src/in_xsf_framework/XSFFile.cpp
+++ b/src/in_xsf_framework/XSFFile.cpp
@@ -301,24 +301,24 @@
 	bool hadReplayGain = false;
 	if (preferredVolumeType == VolumeType::ReplayGainAlbum && !replaygain_album_gain.empty())
 	{
-		gain = convertTo<double>(replaygain_album_gain);
+		gain = ConvertFuncs::To<double>(replaygain_album_gain);
 		hadReplayGain = true;
 	}
 	if (!hadReplayGain && preferredVolumeType != VolumeType::Volume && !replaygain_track_gain.empty())
 	{
-		gain = convertTo<double>(replaygain_track_gain);
+		gain = ConvertFuncs::To<double>(replaygain_track_gain);
 		hadReplayGain = true;
 	}
 	if (hadReplayGain)
 	{
 		double vol = std::pow(10.0, gain / 20.0), peak = 1.0;
 		if (preferredPeakType == PeakType::ReplayGainAlbum && !replaygain_album_peak.empty())
-			peak = convertTo<double>(replaygain_album_peak);
+			peak = ConvertFuncs::To<double>(replaygain_album_peak);
 		else if (preferredPeakType != PeakType::None && !replaygain_track_peak.empty())
-			peak = convertTo<double>(replaygain_track_peak);
+			peak = ConvertFuncs::To<double>(replaygain_track_peak);
 		return !fEqual(peak, 1.0) ? std::min(vol, 1.0 / peak) : vol;
 	}
-	return volume.empty() ? 1.0 : convertTo<double>(volume);
+	return volume.empty() ? 1.0 : ConvertFuncs::To<double>(volume);
 }
 
 std::string XSFFile::FormattedTitleOptionalBlock(const std::string &block, bool &hadReplacement, unsigned level) const

--- a/src/in_xsf_framework/XSFFile.h
+++ b/src/in_xsf_framework/XSFFile.h
@@ -61,7 +61,7 @@
 	std::string GetTagValue(const std::string &name) const;
 	template<typename T> T GetTagValue(const std::string &name, const T &defaultValue) const
 	{
-		return this->GetTagExists(name) ? convertTo<T>(this->GetTagValue(name)) : defaultValue;
+		return this->GetTagExists(name) ? ConvertFuncs::To<T>(this->GetTagValue(name)) : defaultValue;
 	}
 	unsigned long GetLengthMS(unsigned long defaultLength) const;
 	unsigned long GetFadeMS(unsigned long defaultFade) const;

--- a/src/in_xsf_framework/convert.h
+++ b/src/in_xsf_framework/convert.h
@@ -12,47 +12,6 @@
 #include <cmath>
 #include <cstddef>
 #include "windowsh_wrapper.h"
-
-/*
- * Originally the convert* functions came from the C++ FAQ, Miscellaneous Technical Issues:
- * https://isocpp.org/wiki/faq/misc-technical-issues#convert-string-to-any
- *
- * They have been replaced with a couple functions that use the C++11 std::enable_if
- * construct along with various other type traits constructs to use the proper
- * string conversions.
- */
-template<typename T, typename S> inline typename std::enable_if_t<!std::is_enum_v<T> &&std::is_arithmetic_v<T>, T> convertTo(const std::basic_string<S> &s)
-{
-	if (std::is_integral_v<T>)
-	{
-		if (std::is_unsigned_v<T>)
-		{
-			if (std::is_same_v<unsigned long long, std::remove_cv_t<T>>)
-				return static_cast<T>(std::stoull(s));
-			else
-				return static_cast<T>(std::stoul(s));
-		}
-		else if (std::is_same_v<long long, std::remove_cv_t<T>>)
-			return static_cast<T>(std::stoll(s));
-		else if (std::is_same_v<long, std::remove_cv_t<T>>)
-			return static_cast<T>(std::stol(s));
-		else
-			return static_cast<T>(std::stoi(s));
-	}
-	else if (std::is_floating_point_v<T>)
-	{
-		if (std::is_same_v<long double, std::remove_cv_t<T>>)
-			return static_cast<T>(std::stold(s));
-		else if (std::is_same_v<double, std::remove_cv_t<T>>)
-			return static_cast<T>(std::stod(s));
-		else
-			return static_cast<T>(std::stof(s));
-	}
-}
-template<typename T, typename S> inline typename std::enable_if_t<std::is_enum_v<T>, T> convertTo(const std::basic_string<S> &s)
-{
-	return static_cast<T>(convertTo<std::underlying_type_t<T>>(s));
-}
 
 // Miscellaneous conversion functions
 class ConvertFuncs
@@ -69,8 +28,55 @@
 				return false;
 		return true;
 	}
+public:
+	/*
+	 * Originally the convert* functions came from the C++ FAQ, Miscellaneous Technical Issues:
+	 * https://isocpp.org/wiki/faq/misc-technical-issues#convert-string-to-any
+	 *
+	 * They have been replaced with a couple functions that use the C++11 std::enable_if
+	 * construct along with various other type traits constructs to use the proper
+	 * string conversions.
+	 */
+	template<typename T, typename S> static typename std::enable_if_t<!std::is_enum_v<T> && std::is_arithmetic_v<T>, T> To(const std::basic_string<S> &s)
+	{
+		if (std::is_integral_v<T>)
+		{
+			if (std::is_unsigned_v<T>)
+			{
+				if (std::is_same_v<unsigned long long, std::remove_cv_t<T>>)
+					return static_cast<T>(std::stoull(s));
+				else
+					return static_cast<T>(std::stoul(s));
+			}
+			else if (std::is_same_v<long long, std::remove_cv_t<T>>)
+				return static_cast<T>(std::stoll(s));
+			else if (std::is_same_v<long, std::remove_cv_t<T>>)
+				return static_cast<T>(std::stol(s));
+			else
+				return static_cast<T>(std::stoi(s));
+		}
+		else if (std::is_floating_point_v<T>)
+		{
+			if (std::is_same_v<long double, std::remove_cv_t<T>>)
+				return static_cast<T>(std::stold(s));
+			else if (std::is_same_v<double, std::remove_cv_t<T>>)
+				return static_cast<T>(std::stod(s));
+			else
+				return static_cast<T>(std::stof(s));
+		}
+	}
 
-public:
+	template<typename T, typename S> static typename std::enable_if_t<std::is_enum_v<T>, T> To(const std::basic_string<S> &s)
+	{
+		return static_cast<T>(ConvertFuncs::To<std::underlying_type_t<T>>(s));
+	}
+
+	// Comes from https://stackoverflow.com/a/14589519
+	template<typename T> static constexpr typename std::enable_if_t<std::is_enum_v<T>, std::underlying_type_t<T>> ToIntegral(const T &e)
+	{
+		return static_cast<std::underlying_type_t<T>>(e);
+	}
+
 	static unsigned long StringToMS(const std::string &time)
 	{
 		unsigned long hours = 0, minutes = 0;
@@ -99,13 +105,13 @@
 		{
 			if (!ConvertFuncs::IsDigitsOnly(hoursStr))
 				return 0;
-			hours = convertTo<unsigned long>(hoursStr);
+			hours = ConvertFuncs::To<unsigned long>(hoursStr);
 		}
 		if (!minutesStr.empty())
 		{
 			if (!ConvertFuncs::IsDigitsOnly(minutesStr))
 				return 0;
-			minutes = convertTo<unsigned long>(minutesStr);
+			minutes = ConvertFuncs::To<unsigned long>(minutesStr);
 		}
 		if (!secondsStr.empty())
 		{
@@ -114,7 +120,7 @@
 			std::size_t comma = secondsStr.find(',');
 			if (comma != std::string::npos)
 				secondsStr[comma] = '.';
-			seconds = convertTo<double>(secondsStr);
+			seconds = ConvertFuncs::To<double>(secondsStr);
 		}
 		seconds += minutes * 60 + hours * 1440;
 		return static_cast<unsigned long>(std::floor(seconds * 1000 + 0.5));