* 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.
| ... | ... |
@@ -290,7 +290,7 @@ void XSFConfig_NCSF::RefreshSoundView() |
| 290 | 290 |
if (chanId < 8) |
| 291 | 291 |
buf += L"PSG/Noise?"; |
| 292 | 292 |
else if (chanId < 14) |
| 293 |
- buf += std::to_wstring(chn.reg.waveDuty == 7 ? 0 : 12.5 * (chn.reg.waveDuty + 1)) + L"% Square"; |
|
| 293 |
+ buf += ConvertFuncs::TrimDoubleString(std::to_wstring(chn.reg.waveDuty == 7 ? 0 : 12.5 * (chn.reg.waveDuty + 1))) + L"% Square"; |
|
| 294 | 294 |
else |
| 295 | 295 |
buf += L"Noise"; |
| 296 | 296 |
buf += L")"; |
| ... | ... |
@@ -304,7 +304,7 @@ void XSFConfig_NCSF::RefreshSoundView() |
| 304 | 304 |
|
| 305 | 305 |
std::wstring tmpBuf = ConvertFuncs::StringToWString(NumToHexString(chn.reg.timer)).substr(2); |
| 306 | 306 |
buf = L"$" + tmpBuf + L" (";
|
| 307 |
- tmpBuf = std::to_wstring((ARM7_CLOCK / 2) / static_cast<double>(0x10000 - chn.reg.timer) / 8); |
|
| 307 |
+ tmpBuf = ConvertFuncs::TrimDoubleString(std::to_wstring((ARM7_CLOCK / 2) / static_cast<double>(0x10000 - chn.reg.timer) / 8)); |
|
| 308 | 308 |
if (tmpBuf.find('.') != std::wstring::npos)
|
| 309 | 309 |
tmpBuf = tmpBuf.substr(0, tmpBuf.find('.') + 2);
|
| 310 | 310 |
buf += tmpBuf + L" Hz)"; |
| ... | ... |
@@ -68,8 +68,8 @@ void XSFConfig::LoadConfig() |
| 68 | 68 |
this->defaultLength = ConvertFuncs::StringToMS(this->configIO->GetValue("DefaultLength", XSFConfig::initDefaultLength));
|
| 69 | 69 |
this->defaultFade = ConvertFuncs::StringToMS(this->configIO->GetValue("DefaultFade", XSFConfig::initDefaultFade));
|
| 70 | 70 |
this->volume = this->configIO->GetValue("Volume", XSFConfig::initVolume);
|
| 71 |
- this->volumeType = static_cast<VolumeType>(this->configIO->GetValue("VolumeType", static_cast<int>(XSFConfig::initVolumeType)));
|
|
| 72 |
- this->peakType = static_cast<PeakType>(this->configIO->GetValue("PeakType", static_cast<int>(XSFConfig::initPeakType)));
|
|
| 71 |
+ this->volumeType = this->configIO->GetValue("VolumeType", XSFConfig::initVolumeType);
|
|
| 72 |
+ this->peakType = this->configIO->GetValue("PeakType", XSFConfig::initPeakType);
|
|
| 73 | 73 |
this->sampleRate = this->configIO->GetValue("SampleRate", XSFConfig::initSampleRate);
|
| 74 | 74 |
this->titleFormat = this->configIO->GetValue("TitleFormat", XSFConfig::initTitleFormat);
|
| 75 | 75 |
|
| ... | ... |
@@ -228,7 +228,7 @@ INT_PTR CALLBACK XSFConfig::ConfigDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wPa |
| 228 | 228 |
SetWindowTextW(GetDlgItem(hwndDlg, idDefaultFade), ConvertFuncs::MSToWString(this->defaultFade).c_str()); |
| 229 | 229 |
SetWindowTextW(GetDlgItem(hwndDlg, idSkipSilenceOnStartSec), ConvertFuncs::MSToWString(this->skipSilenceOnStartSec).c_str()); |
| 230 | 230 |
SetWindowTextW(GetDlgItem(hwndDlg, idDetectSilenceSec), ConvertFuncs::MSToWString(this->detectSilenceSec).c_str()); |
| 231 |
- SetWindowTextW(GetDlgItem(hwndDlg, idVolume), std::to_wstring(this->volume).c_str()); |
|
| 231 |
+ SetWindowTextW(GetDlgItem(hwndDlg, idVolume), ConvertFuncs::TrimDoubleString(std::to_wstring(this->volume)).c_str()); |
|
| 232 | 232 |
SendMessageW(GetDlgItem(hwndDlg, idReplayGain), CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(L"Disabled")); |
| 233 | 233 |
SendMessageW(GetDlgItem(hwndDlg, idReplayGain), CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(L"Use Volume Tag")); |
| 234 | 234 |
SendMessageW(GetDlgItem(hwndDlg, idReplayGain), CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(L"Track")); |
| ... | ... |
@@ -335,7 +335,7 @@ void XSFConfig::ResetConfigDefaults(HWND hwndDlg) |
| 335 | 335 |
SetWindowTextW(GetDlgItem(hwndDlg, idDefaultFade), ConvertFuncs::StringToWString(XSFConfig::initDefaultFade).c_str()); |
| 336 | 336 |
SetWindowTextW(GetDlgItem(hwndDlg, idSkipSilenceOnStartSec), ConvertFuncs::StringToWString(XSFConfig::initSkipSilenceOnStartSec).c_str()); |
| 337 | 337 |
SetWindowTextW(GetDlgItem(hwndDlg, idDetectSilenceSec), ConvertFuncs::StringToWString(XSFConfig::initDetectSilenceSec).c_str()); |
| 338 |
- SetWindowTextW(GetDlgItem(hwndDlg, idVolume), std::to_wstring(XSFConfig::initVolume).c_str()); |
|
| 338 |
+ SetWindowTextW(GetDlgItem(hwndDlg, idVolume), ConvertFuncs::TrimDoubleString(std::to_wstring(XSFConfig::initVolume)).c_str()); |
|
| 339 | 339 |
SendMessageW(GetDlgItem(hwndDlg, idReplayGain), CB_SETCURSEL, XSFConfig::initVolumeType, 0); |
| 340 | 340 |
SendMessageW(GetDlgItem(hwndDlg, idClipProtect), CB_SETCURSEL, XSFConfig::initPeakType, 0); |
| 341 | 341 |
auto found = std::find(this->supportedSampleRates.begin(), this->supportedSampleRates.end(), XSFConfig::initSampleRate); |
| ... | ... |
@@ -16,6 +16,26 @@ |
| 16 | 16 |
|
| 17 | 17 |
class XSFConfigIO |
| 18 | 18 |
{
|
| 19 |
+private: |
|
| 20 |
+ // enum versions |
|
| 21 |
+ template<typename T> typename std::enable_if_t<std::is_enum_v<T>, T> GetValueInternal(const std::string &name, const T &defaultValue) const |
|
| 22 |
+ {
|
|
| 23 |
+ return convertTo<T>(this->GetValueString(name, std::to_string(static_cast<std::underlying_type_t<T>>(defaultValue)))); |
|
| 24 |
+ } |
|
| 25 |
+ template<typename T> typename std::enable_if_t<std::is_enum_v<T>> SetValueInternal(const std::string &name, const T &value) |
|
| 26 |
+ {
|
|
| 27 |
+ this->SetValueString(name, std::to_string(static_cast<std::underlying_type_t<T>>(value))); |
|
| 28 |
+ } |
|
| 29 |
+ |
|
| 30 |
+ // non-enum versions |
|
| 31 |
+ 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 |
|
| 32 |
+ {
|
|
| 33 |
+ return convertTo<T>(this->GetValueString(name, std::to_string(defaultValue))); |
|
| 34 |
+ } |
|
| 35 |
+ 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) |
|
| 36 |
+ {
|
|
| 37 |
+ this->SetValueString(name, std::to_string(value)); |
|
| 38 |
+ } |
|
| 19 | 39 |
protected: |
| 20 | 40 |
XSFConfigIO() { }
|
| 21 | 41 |
public: |
| ... | ... |
@@ -24,10 +44,10 @@ public: |
| 24 | 44 |
|
| 25 | 45 |
virtual ~XSFConfigIO() { }
|
| 26 | 46 |
virtual void SetValueString(const std::string &name, const std::string &value) = 0; |
| 27 |
- template<typename T> void SetValue(const std::string &name, const T &value) { this->SetValueString(name, std::to_string(value)); }
|
|
| 47 |
+ template<typename T> void SetValue(const std::string &name, const T &value) { this->SetValueInternal(name, value); }
|
|
| 28 | 48 |
void SetValue(const std::string &name, const std::string &value) { this->SetValueString(name, value); }
|
| 29 | 49 |
virtual std::string GetValueString(const std::string &name, const std::string &defaultValue) const = 0; |
| 30 |
- template<typename T> T GetValue(const std::string &name, const T &defaultValue) const { return convertTo<T>(this->GetValueString(name, std::to_string(defaultValue))); }
|
|
| 50 |
+ template<typename T> T GetValue(const std::string &name, const T &defaultValue) const { return this->GetValueInternal(name, defaultValue); }
|
|
| 31 | 51 |
std::string GetValue(const std::string &name, const std::string &defaultValue) const { return this->GetValueString(name, defaultValue); }
|
| 32 | 52 |
virtual void SetHInstance(HINSTANCE) { }
|
| 33 | 53 |
virtual HINSTANCE GetHInstance() const { return nullptr; }
|
| ... | ... |
@@ -130,14 +130,14 @@ public: |
| 130 | 130 |
{
|
| 131 | 131 |
double seconds = time / 1000.0; |
| 132 | 132 |
if (seconds < 60) |
| 133 |
- return std::to_string(seconds); |
|
| 133 |
+ return ConvertFuncs::TrimDoubleString(std::to_string(seconds)); |
|
| 134 | 134 |
unsigned long minutes = static_cast<unsigned long>(seconds) / 60; |
| 135 | 135 |
seconds -= minutes * 60; |
| 136 | 136 |
if (minutes < 60) |
| 137 |
- return std::to_string(minutes) + ":" + (seconds < 10 ? "0" : "") + std::to_string(seconds); |
|
| 137 |
+ return std::to_string(minutes) + ":" + (seconds < 10 ? "0" : "") + ConvertFuncs::TrimDoubleString(std::to_string(seconds)); |
|
| 138 | 138 |
unsigned long hours = minutes / 60; |
| 139 | 139 |
minutes %= 60; |
| 140 |
- return std::to_string(hours) + ":" + (minutes < 10 ? "0" : "") + std::to_string(minutes) + ":" + (seconds < 10 ? "0" : "") + std::to_string(seconds); |
|
| 140 |
+ return std::to_string(hours) + ":" + (minutes < 10 ? "0" : "") + std::to_string(minutes) + ":" + (seconds < 10 ? "0" : "") + ConvertFuncs::TrimDoubleString(std::to_string(seconds)); |
|
| 141 | 141 |
} |
| 142 | 142 |
|
| 143 | 143 |
static std::wstring MSToWString(unsigned long time) |
| ... | ... |
@@ -145,13 +145,22 @@ public: |
| 145 | 145 |
return ConvertFuncs::StringToWString(ConvertFuncs::MSToString(time)); |
| 146 | 146 |
} |
| 147 | 147 |
|
| 148 |
+ // Derived from https://stackoverflow.com/a/13709929 (specifically the comments) |
|
| 149 |
+ template<typename T> static std::basic_string<T> TrimDoubleString(const std::basic_string<T> &str) |
|
| 150 |
+ {
|
|
| 151 |
+ auto strCopy = str; |
|
| 152 |
+ auto lastNonZero = strCopy.find_last_not_of('0');
|
|
| 153 |
+ strCopy.erase(lastNonZero + (lastNonZero == strCopy.find('.') ? 0 : 1));
|
|
| 154 |
+ return strCopy; |
|
| 155 |
+ } |
|
| 156 |
+ |
|
| 148 | 157 |
static std::wstring StringToWString(const std::string &str) |
| 149 | 158 |
{
|
| 150 | 159 |
auto strC = str.c_str(); |
| 151 | 160 |
int bufferSize = MultiByteToWideChar(CP_UTF8, 0, strC, -1, nullptr, 0); |
| 152 | 161 |
auto buffer = std::vector<wchar_t>(bufferSize); |
| 153 | 162 |
MultiByteToWideChar(CP_UTF8, 0, strC, -1, &buffer[0], bufferSize); |
| 154 |
- return std::wstring(buffer.begin(), buffer.end()); |
|
| 163 |
+ return std::wstring(buffer.begin(), buffer.begin() + bufferSize - 1); |
|
| 155 | 164 |
} |
| 156 | 165 |
|
| 157 | 166 |
static std::string WStringToString(const std::wstring &wstr) |
| ... | ... |
@@ -160,6 +169,6 @@ public: |
| 160 | 169 |
int bufferSize = WideCharToMultiByte(CP_UTF8, 0, wstrC, -1, nullptr, 0, nullptr, nullptr); |
| 161 | 170 |
auto buffer = std::vector<char>(bufferSize); |
| 162 | 171 |
WideCharToMultiByte(CP_UTF8, 0, wstrC, -1, &buffer[0], bufferSize, nullptr, nullptr); |
| 163 |
- return std::string(buffer.begin(), buffer.end()); |
|
| 172 |
+ return std::string(buffer.begin(), buffer.begin() + bufferSize - 1); |
|
| 164 | 173 |
} |
| 165 | 174 |
}; |