diff --git a/CMakeLists.txt b/CMakeLists.txt index 587ac55f..31a31655 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -97,7 +97,7 @@ set(SOURCE_FILES src/Phone.cpp src/Phone.h src/Shape.cpp src/Shape.h src/centiseconds.cpp src/centiseconds.h - src/enumTools.h + src/EnumConverter.h src/mouthAnimation.cpp src/mouthAnimation.h src/phoneExtraction.cpp src/phoneExtraction.h src/platformTools.cpp src/platformTools.h diff --git a/src/EnumConverter.h b/src/EnumConverter.h new file mode 100644 index 00000000..e67e4394 --- /dev/null +++ b/src/EnumConverter.h @@ -0,0 +1,99 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +template +class EnumConverter { +public: + EnumConverter() : + initialized(false) + {} + + virtual ~EnumConverter() = default; + + virtual boost::optional tryToString(TEnum value) { + initialize(); + auto it = valueToNameMap.find(value); + return it != valueToNameMap.end() + ? it->second + : boost::optional(); + } + + std::string toString(TEnum value) { + initialize(); + auto result = tryToString(value); + if (!result) { + auto numericValue = static_cast::type>(value); + throw std::invalid_argument(fmt::format("{} is not a valid {} value.", numericValue, typeName)); + } + + return result.value(); + } + + virtual boost::optional tryParse(const std::string& s) { + initialize(); + auto it = lowerCaseNameToValueMap.find(boost::algorithm::to_lower_copy(s)); + return it != lowerCaseNameToValueMap.end() + ? it->second + : boost::optional(); + } + + TEnum parse(const std::string& s) { + initialize(); + auto result = tryParse(s); + if (!result) { + throw std::invalid_argument(fmt::format("{} is not a valid {} value.", s, typeName)); + } + + return result.value(); + } + + std::ostream& write(std::ostream& stream, TEnum value) { + return stream << toString(value); + } + + std::istream& read(std::istream& stream, TEnum& value) { + std::string name; + stream >> name; + value = parse(name); + return stream; + } + + const std::vector& getValues() { + initialize(); + return values; + } + +protected: + using member_data = std::vector>; + + virtual std::string getTypeName() = 0; + virtual member_data getMemberData() = 0; + +private: + void initialize() { + if (initialized) return; + + typeName = getTypeName(); + for (const auto& pair : getMemberData()) { + TEnum value = pair.first; + std::string name = pair.second; + lowerCaseNameToValueMap[boost::algorithm::to_lower_copy(name)] = value; + valueToNameMap[value] = name; + values.push_back(value); + } + initialized = true; + } + + bool initialized; + std::string typeName; + std::map lowerCaseNameToValueMap; + std::map valueToNameMap; + std::vector values; +}; diff --git a/src/Exporter.cpp b/src/Exporter.cpp index 91d20a5d..80fc85fa 100644 --- a/src/Exporter.cpp +++ b/src/Exporter.cpp @@ -1,41 +1,35 @@ #include "Exporter.h" #include -#include #include #include #include using std::string; using boost::property_tree::ptree; -using std::vector; -using std::tuple; -using std::make_tuple; - -template <> -const string& getEnumTypeName() { - static const string name = "ExportFormat"; - return name; + +ExportFormatConverter& ExportFormatConverter::get() { + static ExportFormatConverter converter; + return converter; +} + +string ExportFormatConverter::getTypeName() { + return "ExportFormat"; } -template <> -const vector>& getEnumMembers() { - static const vector> values = { - make_tuple(ExportFormat::TSV, "TSV"), - make_tuple(ExportFormat::XML, "XML"), - make_tuple(ExportFormat::JSON, "JSON") +EnumConverter::member_data ExportFormatConverter::getMemberData() { + return member_data{ + { ExportFormat::TSV, "TSV" }, + { ExportFormat::XML, "XML" }, + { ExportFormat::JSON, "JSON" } }; - return values; } std::ostream& operator<<(std::ostream& stream, ExportFormat value) { - return stream << enumToString(value); + return ExportFormatConverter::get().write(stream, value); } std::istream& operator>>(std::istream& stream, ExportFormat& value) { - string name; - stream >> name; - value = parseEnum(name); - return stream; + return ExportFormatConverter::get().read(stream, value); } // Makes sure there is at least one mouth shape diff --git a/src/Exporter.h b/src/Exporter.h index f2061a33..cd665027 100644 --- a/src/Exporter.h +++ b/src/Exporter.h @@ -11,11 +11,13 @@ enum class ExportFormat { JSON }; -template<> -const std::string& getEnumTypeName(); - -template<> -const std::vector>& getEnumMembers(); +class ExportFormatConverter : public EnumConverter { +public: + static ExportFormatConverter& get(); +protected: + std::string getTypeName() override; + member_data getMemberData() override; +}; std::ostream& operator<<(std::ostream& stream, ExportFormat value); diff --git a/src/Phone.cpp b/src/Phone.cpp index 1bf75c14..752c9e0e 100644 --- a/src/Phone.cpp +++ b/src/Phone.cpp @@ -2,78 +2,72 @@ #include "Phone.h" using std::string; -using std::vector; -using std::tuple; -using std::make_tuple; -template <> -const string& getEnumTypeName() { - static const string name = "Shape"; - return name; +PhoneConverter& PhoneConverter::get() { + static PhoneConverter converter; + return converter; } -template <> -const vector>& getEnumMembers() { - static const vector> values = { - make_tuple(Phone::None, "None"), - make_tuple(Phone::Unknown, "Unknown"), - make_tuple(Phone::AO, "AO"), - make_tuple(Phone::AA, "AA"), - make_tuple(Phone::IY, "IY"), - make_tuple(Phone::UW, "UW"), - make_tuple(Phone::EH, "EH"), - make_tuple(Phone::IH, "IH"), - make_tuple(Phone::UH, "UH"), - make_tuple(Phone::AH, "AH"), - make_tuple(Phone::AE, "AE"), - make_tuple(Phone::EY, "EY"), - make_tuple(Phone::AY, "AY"), - make_tuple(Phone::OW, "OW"), - make_tuple(Phone::AW, "AW"), - make_tuple(Phone::OY, "OY"), - make_tuple(Phone::ER, "ER"), - make_tuple(Phone::P, "P"), - make_tuple(Phone::B, "B"), - make_tuple(Phone::T, "T"), - make_tuple(Phone::D, "D"), - make_tuple(Phone::K, "K"), - make_tuple(Phone::G, "G"), - make_tuple(Phone::CH, "CH"), - make_tuple(Phone::JH, "JH"), - make_tuple(Phone::F, "F"), - make_tuple(Phone::V, "V"), - make_tuple(Phone::TH, "TH"), - make_tuple(Phone::DH, "DH"), - make_tuple(Phone::S, "S"), - make_tuple(Phone::Z, "Z"), - make_tuple(Phone::SH, "SH"), - make_tuple(Phone::ZH, "ZH"), - make_tuple(Phone::HH, "HH"), - make_tuple(Phone::M, "M"), - make_tuple(Phone::N, "N"), - make_tuple(Phone::NG, "NG"), - make_tuple(Phone::L, "L"), - make_tuple(Phone::R, "R"), - make_tuple(Phone::Y, "Y"), - make_tuple(Phone::W, "W") +string PhoneConverter::getTypeName() { + return "Phone"; +} + +EnumConverter::member_data PhoneConverter::getMemberData() { + return member_data{ + { Phone::None, "None" }, + { Phone::Unknown, "Unknown" }, + { Phone::AO, "AO" }, + { Phone::AA, "AA" }, + { Phone::IY, "IY" }, + { Phone::UW, "UW" }, + { Phone::EH, "EH" }, + { Phone::IH, "IH" }, + { Phone::UH, "UH" }, + { Phone::AH, "AH" }, + { Phone::AE, "AE" }, + { Phone::EY, "EY" }, + { Phone::AY, "AY" }, + { Phone::OW, "OW" }, + { Phone::AW, "AW" }, + { Phone::OY, "OY" }, + { Phone::ER, "ER" }, + { Phone::P, "P" }, + { Phone::B, "B" }, + { Phone::T, "T" }, + { Phone::D, "D" }, + { Phone::K, "K" }, + { Phone::G, "G" }, + { Phone::CH, "CH" }, + { Phone::JH, "JH" }, + { Phone::F, "F" }, + { Phone::V, "V" }, + { Phone::TH, "TH" }, + { Phone::DH, "DH" }, + { Phone::S, "S" }, + { Phone::Z, "Z" }, + { Phone::SH, "SH" }, + { Phone::ZH, "ZH" }, + { Phone::HH, "HH" }, + { Phone::M, "M" }, + { Phone::N, "N" }, + { Phone::NG, "NG" }, + { Phone::L, "L" }, + { Phone::R, "R" }, + { Phone::Y, "Y" }, + { Phone::W, "W" } }; - return values; } -template<> -Phone parseEnum(const string& s) { +boost::optional PhoneConverter::tryParse(const string& s) { if (s == "SIL") return Phone::None; - Phone result; - return tryParseEnum(s, result) ? result : Phone::Unknown; + auto result = EnumConverter::tryParse(s); + return result ? result : Phone::Unknown; } std::ostream& operator<<(std::ostream& stream, Phone value) { - return stream << enumToString(value); + return PhoneConverter::get().write(stream, value); } std::istream& operator>>(std::istream& stream, Phone& value) { - string name; - stream >> name; - value = parseEnum(name); - return stream; + return PhoneConverter::get().read(stream, value); } diff --git a/src/Phone.h b/src/Phone.h index 0e146292..76894b90 100644 --- a/src/Phone.h +++ b/src/Phone.h @@ -1,6 +1,6 @@ #pragma once -#include "enumTools.h" +#include "EnumConverter.h" // Defines a subset of the Arpabet enum class Phone { @@ -71,14 +71,15 @@ enum class Phone { W // [w] as in [w]ay }; -template<> -const std::string& getEnumTypeName(); - -template<> -const std::vector>& getEnumMembers(); - -template<> -Phone parseEnum(const std::string& s); +class PhoneConverter : public EnumConverter { +public: + static PhoneConverter& get(); +protected: + std::string getTypeName() override; + member_data getMemberData() override; +public: + boost::optional tryParse(const std::string& s) override; +}; std::ostream& operator<<(std::ostream& stream, Phone value); diff --git a/src/Shape.cpp b/src/Shape.cpp index 4870fbe0..e9a5ac5d 100644 --- a/src/Shape.cpp +++ b/src/Shape.cpp @@ -1,40 +1,33 @@ #include "Shape.h" -#include - using std::string; -using std::vector; -using std::tuple; -using std::make_tuple; -template <> -const string& getEnumTypeName() { - static const string name = "Shape"; - return name; +ShapeConverter& ShapeConverter::get() { + static ShapeConverter converter; + return converter; +} + +string ShapeConverter::getTypeName() { + return "Shape"; } -template <> -const vector>& getEnumMembers() { - static const vector> values = { - make_tuple(Shape::A, "A"), - make_tuple(Shape::B, "B"), - make_tuple(Shape::C, "C"), - make_tuple(Shape::D, "D"), - make_tuple(Shape::E, "E"), - make_tuple(Shape::F, "F"), - make_tuple(Shape::G, "G"), - make_tuple(Shape::H, "H") +EnumConverter::member_data ShapeConverter::getMemberData() { + return member_data{ + { Shape::A, "A" }, + { Shape::B, "B" }, + { Shape::C, "C" }, + { Shape::D, "D" }, + { Shape::E, "E" }, + { Shape::F, "F" }, + { Shape::G, "G" }, + { Shape::H, "H" } }; - return values; } std::ostream& operator<<(std::ostream& stream, Shape value) { - return stream << enumToString(value); + return ShapeConverter::get().write(stream, value); } std::istream& operator>>(std::istream& stream, Shape& value) { - string name; - stream >> name; - value = parseEnum(name); - return stream; + return ShapeConverter::get().read(stream, value); } diff --git a/src/Shape.h b/src/Shape.h index c9e74aa4..3b499048 100644 --- a/src/Shape.h +++ b/src/Shape.h @@ -1,8 +1,7 @@ #pragma once #include -#include -#include "enumTools.h" +#include "EnumConverter.h" // The classic Hanna-Barbera mouth shapes A-F phus the common supplements G-H // For reference, see http://sunewatts.dk/lipsync/lipsync/article_02.php @@ -19,11 +18,13 @@ enum class Shape { H // L }; -template<> -const std::string& getEnumTypeName(); - -template<> -const std::vector>& getEnumMembers(); +class ShapeConverter : public EnumConverter { +public: + static ShapeConverter& get(); +protected: + std::string getTypeName() override; + member_data getMemberData() override; +}; std::ostream& operator<<(std::ostream& stream, Shape value); diff --git a/src/enumTools.h b/src/enumTools.h deleted file mode 100644 index d51a7e0c..00000000 --- a/src/enumTools.h +++ /dev/null @@ -1,94 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -template -const std::string& getEnumTypeName(); - -template -const std::vector>& getEnumMembers(); - -namespace detail { - - template - std::map createLowerCaseNameToValueMap() { - std::map map; - for (const auto& pair : getEnumMembers()) { - map[boost::algorithm::to_lower_copy(std::get(pair))] = std::get(pair); - } - return map; - } - - template - std::map createValueToNameMap() { - std::map map; - for (const auto& pair : getEnumMembers()) { - map[std::get(pair)] = std::get(pair); - } - return map; - } - - template - std::vector getEnumValues() { - std::vector result; - for (const auto& pair : getEnumMembers()) { - result.push_back(std::get(pair)); - } - return result; - } - -} - -template -bool tryParseEnum(const std::string& s, T& result) { - static const std::map lookup = detail::createLowerCaseNameToValueMap(); - auto it = lookup.find(boost::algorithm::to_lower_copy(s)); - if (it == lookup.end()) return false; - - result = it->second; - return true; -} - -template -T parseEnum(const std::string& s) { - T result; - if (!tryParseEnum(s, result)) { - throw std::invalid_argument(fmt::format("{} is not a valid {} value.", s, getEnumTypeName())); - } - - return result; -} - -template -bool tryEnumToString(T value, std::string& result) { - static const std::map lookup = detail::createValueToNameMap(); - auto it = lookup.find(value); - if (it == lookup.end()) return false; - - result = it->second; - return true; -} - -template -std::string enumToString(T value) { - std::string result; - if (!tryEnumToString(value, result)) { - throw std::invalid_argument(fmt::format( - "{} is not a valid {} value.", - static_cast::type>(value), - getEnumTypeName())); - } - - return result; -} - -template -const std::vector& getEnumValues() { - static const auto result = detail::getEnumValues(); - return result; -} \ No newline at end of file diff --git a/src/logging.cpp b/src/logging.cpp index c93a748d..3325d144 100644 --- a/src/logging.cpp +++ b/src/logging.cpp @@ -5,39 +5,35 @@ using namespace logging; using std::string; using std::vector; -using std::tuple; -using std::make_tuple; using std::shared_ptr; using std::lock_guard; -template <> -const string& getEnumTypeName() { - static const string name = "LogLevel"; - return name; -} - -template <> -const vector>& getEnumMembers() { - static const vector> values = { - make_tuple(Level::Trace, "Trace"), - make_tuple(Level::Debug, "Debug"), - make_tuple(Level::Info, "Info"), - make_tuple(Level::Warn, "Warn"), - make_tuple(Level::Error, "Error"), - make_tuple(Level::Fatal, "Fatal") +LevelConverter& LevelConverter::get() { + static LevelConverter converter; + return converter; +} + +string LevelConverter::getTypeName() { + return "Level"; +} + +EnumConverter::member_data LevelConverter::getMemberData() { + return member_data { + { Level::Trace, "Trace" }, + { Level::Debug, "Debug" }, + { Level::Info, "Info" }, + { Level::Warn, "Warn" }, + { Level::Error, "Error" }, + { Level::Fatal, "Fatal" } }; - return values; } -std::ostream& operator<<(std::ostream& stream, Level value) { - return stream << enumToString(value); +std::ostream& logging::operator<<(std::ostream& stream, Level value) { + return LevelConverter::get().write(stream, value); } -std::istream& operator>>(std::istream& stream, Level& value) { - string name; - stream >> name; - value = parseEnum(name); - return stream; +std::istream& logging::operator>>(std::istream& stream, Level& value) { + return LevelConverter::get().read(stream, value); } Entry::Entry(Level level, const string& message) : @@ -77,7 +73,7 @@ void StreamSink::receive(const Entry& entry) { } StdErrSink::StdErrSink(shared_ptr formatter) : - StreamSink(std::shared_ptr(&std::cerr, [](void*){}), formatter) + StreamSink(std::shared_ptr(&std::cerr, [](void*) {}), formatter) {} PausableSink::PausableSink(shared_ptr innerSink) : diff --git a/src/logging.h b/src/logging.h index 1b9b0a37..84ebad90 100644 --- a/src/logging.h +++ b/src/logging.h @@ -3,10 +3,9 @@ #include #include #include -#include -#include "enumTools.h" #include "tools.h" #include "Timed.h" +#include "EnumConverter.h" namespace logging { @@ -20,19 +19,17 @@ namespace logging { EndSentinel }; -} - -template<> -const std::string& getEnumTypeName(); - -template<> -const std::vector>& getEnumMembers(); - -std::ostream& operator<<(std::ostream& stream, logging::Level value); + class LevelConverter : public EnumConverter { + public: + static LevelConverter& get(); + protected: + std::string getTypeName() override; + member_data getMemberData() override; + }; -std::istream& operator>>(std::istream& stream, logging::Level& value); + std::ostream& operator<<(std::ostream& stream, Level value); -namespace logging { + std::istream& operator>>(std::istream& stream, Level& value); struct Entry { Entry(Level level, const std::string& message); diff --git a/src/main.cpp b/src/main.cpp index 3fed5664..35f3e199 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -84,12 +84,12 @@ int main(int argc, char *argv[]) { tclap::CmdLine cmd(appName, argumentValueSeparator, appVersion); cmd.setExceptionHandling(false); cmd.setOutput(new NiceCmdLineOutput()); - auto logLevels = vector(getEnumValues()); + auto logLevels = vector(logging::LevelConverter::get().getValues()); tclap::ValuesConstraint logLevelConstraint(logLevels); tclap::ValueArg logLevel("", "logLevel", "The minimum log level to log", false, logging::Level::Debug, &logLevelConstraint, cmd); tclap::ValueArg logFileName("", "logFile", "The log file path.", false, string(), "string", cmd); tclap::ValueArg dialog("d", "dialog", "The text of the dialog.", false, string(), "string", cmd); - auto exportFormats = vector(getEnumValues()); + auto exportFormats = vector(ExportFormatConverter::get().getValues()); tclap::ValuesConstraint exportFormatConstraint(exportFormats); tclap::ValueArg exportFormat("f", "exportFormat", "The export format.", false, ExportFormat::TSV, &exportFormatConstraint, cmd); tclap::UnlabeledValueArg inputFileName("inputFile", "The input file. Must be a sound file in WAVE format.", true, "", "string", cmd); diff --git a/src/phoneExtraction.cpp b/src/phoneExtraction.cpp index 3532f812..2c768b36 100644 --- a/src/phoneExtraction.cpp +++ b/src/phoneExtraction.cpp @@ -275,7 +275,7 @@ Timeline getPhoneAlignment(const vector& wordIds, unique_ptrstart); centiseconds duration(phoneEntry->duration); - Timed timedPhone(start, start + duration, parseEnum(phoneName)); + Timed timedPhone(start, start + duration, PhoneConverter::get().parse(phoneName)); result.set(timedPhone); logging::logTimedEvent("phone", timedPhone);