Cleanup: extract VAlign to its own files.
[libdcp.git] / src / types.cc
index 4f089e2eec9a5d3644cf4f5264acf96abd96e36f..809319225eb997baa29df9019fe91cb6e76dbb63 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2012-2019 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2012-2021 Carl Hetherington <cth@carlh.net>
 
     This file is part of libdcp.
 
     files in the program, then also delete it here.
 */
 
-#include "raw_convert.h"
-#include "types.h"
-#include "exceptions.h"
+
+/** @file  src/types.cc
+ *  @brief Miscellaneous types
+ */
+
+
 #include "compose.hpp"
 #include "dcp_assert.h"
+#include "exceptions.h"
+#include "raw_convert.h"
+#include "types.h"
+#include "warnings.h"
+LIBDCP_DISABLE_WARNINGS
 #include <libxml++/libxml++.h>
+LIBDCP_ENABLE_WARNINGS
 #include <boost/algorithm/string.hpp>
-#include <boost/foreach.hpp>
 #include <string>
 #include <vector>
 #include <cmath>
 #include <cstdio>
 #include <iomanip>
 
+
 using std::string;
 using std::ostream;
 using std::vector;
 using namespace dcp;
 using namespace boost;
 
+
 bool dcp::operator== (dcp::Size const & a, dcp::Size const & b)
 {
        return (a.width == b.width && a.height == b.height);
 }
 
+
 bool dcp::operator!= (dcp::Size const & a, dcp::Size const & b)
 {
        return !(a == b);
 }
 
-ostream& dcp::operator<< (ostream& s, dcp::Size const & a)
-{
-       s << a.width << "x" << a.height;
-       return s;
-}
 
-/** Construct a Fraction from a string of the form <numerator> <denominator>
+/** Construct a Fraction from a string of the form "numerator denominator"
  *  e.g. "1 3".
  */
 Fraction::Fraction (string s)
@@ -75,49 +81,34 @@ Fraction::Fraction (string s)
        vector<string> b;
        split (b, s, is_any_of (" "));
        if (b.size() != 2) {
-               boost::throw_exception (XMLError ("malformed fraction " + s + " in XML node"));
+               boost::throw_exception (XMLError("malformed fraction " + s + " in XML node"));
        }
        numerator = raw_convert<int> (b[0]);
        denominator = raw_convert<int> (b[1]);
 }
 
+
 string
 Fraction::as_string () const
 {
        return String::compose ("%1 %2", numerator, denominator);
 }
 
+
 bool
 dcp::operator== (Fraction const & a, Fraction const & b)
 {
        return (a.numerator == b.numerator && a.denominator == b.denominator);
 }
 
+
 bool
 dcp::operator!= (Fraction const & a, Fraction const & b)
 {
        return (a.numerator != b.numerator || a.denominator != b.denominator);
 }
 
-ostream&
-dcp::operator<< (ostream& s, Fraction const & f)
-{
-       s << f.numerator << "/" << f.denominator;
-       return s;
-}
-
-/** Construct a Colour, initialising it to black. */
-Colour::Colour ()
-       : r (0)
-       , g (0)
-       , b (0)
-{
-
-}
 
-/** Construct a Colour from R, G and B.  The values run between
- *  0 and 255.
- */
 Colour::Colour (int r_, int g_, int b_)
        : r (r_)
        , g (g_)
@@ -126,10 +117,7 @@ Colour::Colour (int r_, int g_, int b_)
 
 }
 
-/** Construct a Colour from an ARGB hex string; the alpha value is ignored.
- *  @param argb_hex A string of the form AARRGGBB, where e.g. RR is a two-character
- *  hex value.
- */
+
 Colour::Colour (string argb_hex)
 {
        int alpha;
@@ -138,9 +126,7 @@ Colour::Colour (string argb_hex)
        }
 }
 
-/** @return An ARGB string of the form AARRGGBB, where e.g. RR is a two-character
- *  hex value.  The alpha value will always be FF (ie 255; maximum alpha).
- */
+
 string
 Colour::to_argb_string () const
 {
@@ -149,9 +135,7 @@ Colour::to_argb_string () const
        return buffer;
 }
 
-/** @return An RGB string of the form RRGGBB, where e.g. RR is a two-character
- *  hex value.
- */
+
 string
 Colour::to_rgb_string () const
 {
@@ -160,314 +144,176 @@ Colour::to_rgb_string () const
        return buffer;
 }
 
-/** operator== for Colours.
- *  @param a First colour to compare.
- *  @param b Second colour to compare.
- */
+
 bool
 dcp::operator== (Colour const & a, Colour const & b)
 {
        return (a.r == b.r && a.g == b.g && a.b == b.b);
 }
 
-/** operator!= for Colours.
- *  @param a First colour to compare.
- *  @param b Second colour to compare.
- */
+
 bool
 dcp::operator!= (Colour const & a, Colour const & b)
 {
        return !(a == b);
 }
 
-ostream &
-dcp::operator<< (ostream& s, Colour const & c)
-{
-       s << "(" << c.r << ", " << c.g << ", " << c.b << ")";
-       return s;
-}
 
 string
 dcp::effect_to_string (Effect e)
 {
        switch (e) {
-       case NONE:
+       case Effect::NONE:
                return "none";
-       case BORDER:
+       case Effect::BORDER:
                return "border";
-       case SHADOW:
+       case Effect::SHADOW:
                return "shadow";
        }
 
-       boost::throw_exception (MiscError ("unknown effect type"));
+       boost::throw_exception (MiscError("unknown effect type"));
 }
 
+
 Effect
 dcp::string_to_effect (string s)
 {
        if (s == "none") {
-               return NONE;
+               return Effect::NONE;
        } else if (s == "border") {
-               return BORDER;
+               return Effect::BORDER;
        } else if (s == "shadow") {
-               return SHADOW;
+               return Effect::SHADOW;
        }
 
-       boost::throw_exception (ReadError ("unknown subtitle effect type"));
+       boost::throw_exception (ReadError("unknown subtitle effect type"));
 }
 
+
 string
 dcp::halign_to_string (HAlign h)
 {
        switch (h) {
-       case HALIGN_LEFT:
+       case HAlign::LEFT:
                return "left";
-       case HALIGN_CENTER:
+       case HAlign::CENTER:
                return "center";
-       case HALIGN_RIGHT:
+       case HAlign::RIGHT:
                return "right";
        }
 
-       boost::throw_exception (MiscError ("unknown subtitle halign type"));
+       boost::throw_exception (MiscError("unknown subtitle halign type"));
 }
 
+
 HAlign
 dcp::string_to_halign (string s)
 {
        if (s == "left") {
-               return HALIGN_LEFT;
+               return HAlign::LEFT;
        } else if (s == "center") {
-               return HALIGN_CENTER;
+               return HAlign::CENTER;
        } else if (s == "right") {
-               return HALIGN_RIGHT;
-       }
-
-       boost::throw_exception (ReadError ("unknown subtitle halign type"));
-}
-
-string
-dcp::valign_to_string (VAlign v)
-{
-       switch (v) {
-       case VALIGN_TOP:
-               return "top";
-       case VALIGN_CENTER:
-               return "center";
-       case VALIGN_BOTTOM:
-               return "bottom";
+               return HAlign::RIGHT;
        }
 
-       boost::throw_exception (MiscError ("unknown subtitle valign type"));
+       boost::throw_exception (ReadError("unknown subtitle halign type"));
 }
 
-VAlign
-dcp::string_to_valign (string s)
-{
-       if (s == "top") {
-               return VALIGN_TOP;
-       } else if (s == "center") {
-               return VALIGN_CENTER;
-       } else if (s == "bottom") {
-               return VALIGN_BOTTOM;
-       }
-
-       boost::throw_exception (ReadError ("unknown subtitle valign type"));
-}
 
 string
 dcp::direction_to_string (Direction v)
 {
        switch (v) {
-       case DIRECTION_LTR:
+       case Direction::LTR:
                return "ltr";
-       case DIRECTION_RTL:
+       case Direction::RTL:
                return "rtl";
-       case DIRECTION_TTB:
+       case Direction::TTB:
                return "ttb";
-       case DIRECTION_BTT:
+       case Direction::BTT:
                return "btt";
        }
 
-       boost::throw_exception (MiscError ("unknown subtitle direction type"));
+       boost::throw_exception (MiscError("unknown subtitle direction type"));
 }
 
+
 Direction
 dcp::string_to_direction (string s)
 {
        if (s == "ltr" || s == "horizontal") {
-               return DIRECTION_LTR;
+               return Direction::LTR;
        } else if (s == "rtl") {
-               return DIRECTION_RTL;
+               return Direction::RTL;
        } else if (s == "ttb" || s == "vertical") {
-               return DIRECTION_TTB;
+               return Direction::TTB;
        } else if (s == "btt") {
-               return DIRECTION_BTT;
-       }
-
-       boost::throw_exception (ReadError ("unknown subtitle direction type"));
-}
-
-/** Convert a content kind to a string which can be used in a
- *  &lt;ContentKind&gt; node.
- *  @param kind ContentKind.
- *  @return string.
- */
-string
-dcp::content_kind_to_string (ContentKind kind)
-{
-       switch (kind) {
-       case FEATURE:
-               return "feature";
-       case SHORT:
-               return "short";
-       case TRAILER:
-               return "trailer";
-       case TEST:
-               return "test";
-       case TRANSITIONAL:
-               return "transitional";
-       case RATING:
-               return "rating";
-       case TEASER:
-               return "teaser";
-       case POLICY:
-               return "policy";
-       case PUBLIC_SERVICE_ANNOUNCEMENT:
-               return "psa";
-       case ADVERTISEMENT:
-               return "advertisement";
-       case EPISODE:
-               return "episode";
-       case PROMO:
-               return "promo";
+               return Direction::BTT;
        }
 
-       DCP_ASSERT (false);
+       boost::throw_exception (ReadError("unknown subtitle direction type"));
 }
 
-/** Convert a string from a &lt;ContentKind&gt; node to a libdcp ContentKind.
- *  Reasonably tolerant about varying case.
- *  @param kind Content kind string.
- *  @return libdcp ContentKind.
- */
-dcp::ContentKind
-dcp::content_kind_from_string (string kind)
-{
-       transform (kind.begin(), kind.end(), kind.begin(), ::tolower);
-
-       if (kind == "feature") {
-               return FEATURE;
-       } else if (kind == "short") {
-               return SHORT;
-       } else if (kind == "trailer") {
-               return TRAILER;
-       } else if (kind == "test") {
-               return TEST;
-       } else if (kind == "transitional") {
-               return TRANSITIONAL;
-       } else if (kind == "rating") {
-               return RATING;
-       } else if (kind == "teaser") {
-               return TEASER;
-       } else if (kind == "policy") {
-               return POLICY;
-       } else if (kind == "psa") {
-               return PUBLIC_SERVICE_ANNOUNCEMENT;
-       } else if (kind == "advertisement") {
-               return ADVERTISEMENT;
-       } else if (kind == "episode") {
-               return EPISODE;
-       } else if (kind == "promo") {
-               return PROMO;
-       }
-
-       throw BadContentKindError (kind);
-}
 
 string
 dcp::marker_to_string (dcp::Marker m)
 {
        switch (m) {
-       case FFOC:
+       case Marker::FFOC:
                return "FFOC";
-       case LFOC:
+       case Marker::LFOC:
                return "LFOC";
-       case FFTC:
+       case Marker::FFTC:
                return "FFTC";
-       case LFTC:
+       case Marker::LFTC:
                return "LFTC";
-       case FFOI:
+       case Marker::FFOI:
                return "FFOI";
-       case LFOI:
+       case Marker::LFOI:
                return "LFOI";
-       case FFEC:
+       case Marker::FFEC:
                return "FFEC";
-       case LFEC:
+       case Marker::LFEC:
                return "LFEC";
-       case FFMC:
+       case Marker::FFMC:
                return "FFMC";
-       case LFMC:
+       case Marker::LFMC:
                return "LFMC";
        }
 
        DCP_ASSERT (false);
 }
 
+
 dcp::Marker
 dcp::marker_from_string (string s)
 {
        if (s == "FFOC") {
-               return FFOC;
+               return Marker::FFOC;
        } else if (s == "LFOC") {
-               return LFOC;
+               return Marker::LFOC;
        } else if (s == "FFTC") {
-               return FFTC;
+               return Marker::FFTC;
        } else if (s == "LFTC") {
-               return LFTC;
+               return Marker::LFTC;
        } else if (s == "FFOI") {
-               return FFOI;
+               return Marker::FFOI;
        } else if (s == "LFOI") {
-               return LFOI;
+               return Marker::LFOI;
        } else if (s == "FFEC") {
-               return FFEC;
+               return Marker::FFEC;
        } else if (s == "LFEC") {
-               return LFEC;
+               return Marker::LFEC;
        } else if (s == "FFMC") {
-               return FFMC;
+               return Marker::FFMC;
        } else if (s == "LFMC") {
-               return LFMC;
+               return Marker::LFMC;
        }
 
        DCP_ASSERT (false);
 }
 
-Rating::Rating (cxml::ConstNodePtr node)
-{
-       agency = node->string_child("Agency");
-       label = node->string_child("Label");
-       node->done ();
-}
-
-void
-Rating::as_xml (xmlpp::Element* parent) const
-{
-       parent->add_child("Agency")->add_child_text(agency);
-       parent->add_child("Label")->add_child_text(label);
-}
-
-bool
-dcp::operator== (Rating const & a, Rating const & b)
-{
-       return a.agency == b.agency && a.label == b.label;
-}
-
-ostream &
-dcp::operator<< (ostream& s, Rating const & r)
-{
-       s << r.agency << " " << r.label;
-       return s;
-}
-
 
 ContentVersion::ContentVersion ()
        : id ("urn:uuid:" + make_uuid())
@@ -477,9 +323,10 @@ ContentVersion::ContentVersion ()
 
 
 ContentVersion::ContentVersion (cxml::ConstNodePtr node)
+       : id(node->string_child("Id"))
+       , label_text(node->string_child("LabelText"))
 {
-       id = node->string_child("Id");
-       label_text = node->string_child("LabelText");
+
 }
 
 
@@ -494,16 +341,17 @@ ContentVersion::ContentVersion (string label_text_)
 void
 ContentVersion::as_xml (xmlpp::Element* parent) const
 {
-       xmlpp::Node* cv = parent->add_child("ContentVersion");
+       auto cv = parent->add_child("ContentVersion");
        cv->add_child("Id")->add_child_text(id);
        cv->add_child("LabelText")->add_child_text(label_text);
 }
 
 
 Luminance::Luminance (cxml::ConstNodePtr node)
+       : _value(raw_convert<float>(node->content()))
+       , _unit(string_to_unit(node->string_attribute("units")))
 {
-       _unit = string_to_unit (node->string_attribute("units"));
-       _value = raw_convert<float> (node->content());
+
 }
 
 
@@ -528,7 +376,7 @@ Luminance::set_value (float v)
 void
 Luminance::as_xml (xmlpp::Element* parent, string ns) const
 {
-       xmlpp::Element* lum = parent->add_child("Luminance", ns);
+       auto lum = parent->add_child("Luminance", ns);
        lum->set_attribute("units", unit_to_string(_unit));
        lum->add_child_text(raw_convert<string>(_value, 3));
 }
@@ -538,15 +386,15 @@ string
 Luminance::unit_to_string (Unit u)
 {
        switch (u) {
-       case CANDELA_PER_SQUARE_METRE:
+       case Unit::CANDELA_PER_SQUARE_METRE:
                return "candela-per-square-metre";
-       case FOOT_LAMBERT:
+       case Unit::FOOT_LAMBERT:
                return "foot-lambert";
        default:
                DCP_ASSERT (false);
        }
 
-       return "";
+       return {};
 }
 
 
@@ -563,6 +411,20 @@ Luminance::string_to_unit (string u)
 }
 
 
+float
+Luminance::value_in_foot_lamberts () const
+{
+       switch (_unit) {
+       case Unit::CANDELA_PER_SQUARE_METRE:
+               return _value / 3.426;
+       case Unit::FOOT_LAMBERT:
+               return _value;
+       default:
+               DCP_ASSERT (false);
+       }
+}
+
+
 bool
 dcp::operator== (Luminance const& a, Luminance const& b)
 {
@@ -574,16 +436,23 @@ MainSoundConfiguration::MainSoundConfiguration (string s)
 {
        vector<string> parts;
        boost::split (parts, s, boost::is_any_of("/"));
-       if (parts.size() != 2) {
-               throw MainSoundConfigurationError (s);
+       if (parts.empty()) {
+               throw MainSoundConfigurationError(s);
        }
 
        if (parts[0] == "51") {
-               _field = FIVE_POINT_ONE;
+               _field = MCASoundField::FIVE_POINT_ONE;
        } else if (parts[0] == "71") {
-               _field = SEVEN_POINT_ONE;
+               _field = MCASoundField::SEVEN_POINT_ONE;
        } else {
-               throw MainSoundConfigurationError (s);
+               _field = MCASoundField::OTHER;
+       }
+
+       if (parts.size() < 2) {
+               /* I think it's OK to just have the sound field descriptor with no channels, though
+                * to me it's not clear and I might be wrong.
+                */
+               return;
        }
 
        vector<string> channels;
@@ -593,7 +462,7 @@ MainSoundConfiguration::MainSoundConfiguration (string s)
                throw MainSoundConfigurationError (s);
        }
 
-       BOOST_FOREACH (string i, channels) {
+       for (auto i: channels) {
                if (i == "-") {
                        _channels.push_back(optional<Channel>());
                } else {
@@ -614,13 +483,18 @@ string
 MainSoundConfiguration::to_string () const
 {
        string c;
-       if (_field == FIVE_POINT_ONE) {
+       switch (_field) {
+       case MCASoundField::FIVE_POINT_ONE:
                c = "51/";
-       } else {
+               break;
+       case MCASoundField::SEVEN_POINT_ONE:
                c = "71/";
+               break;
+       default:
+               DCP_ASSERT(false);
        }
 
-       BOOST_FOREACH (optional<Channel> i, _channels) {
+       for (auto i: _channels) {
                if (!i) {
                        c += "-,";
                } else {
@@ -656,16 +530,15 @@ string
 dcp::status_to_string (Status s)
 {
        switch (s) {
-       case FINAL:
+       case Status::FINAL:
                return "final";
-       case TEMP:
+       case Status::TEMP:
                return "temp";
-       case PRE:
+       case Status::PRE:
                return "pre";
        default:
                DCP_ASSERT (false);
        }
-
 }
 
 
@@ -673,11 +546,11 @@ Status
 dcp::string_to_status (string s)
 {
        if (s == "final") {
-               return FINAL;
+               return Status::FINAL;
        } else if (s == "temp") {
-               return TEMP;
+               return Status::TEMP;
        } else if (s == "pre") {
-               return PRE;
+               return Status::PRE;
        }
 
        DCP_ASSERT (false);
@@ -687,32 +560,34 @@ dcp::string_to_status (string s)
 Channel
 dcp::mca_id_to_channel (string id)
 {
-       if (id == "L") {
-               return LEFT;
-       } else if (id == "R") {
-               return RIGHT;
-       } else if (id == "C") {
-               return CENTRE;
-       } else if (id == "LFE") {
-               return LFE;
-       } else if (id == "Ls" || id == "Lss") {
-               return LS;
-       } else if (id == "Rs" || id == "Rss") {
-               return RS;
-       } else if (id == "HI") {
-               return HI;
-       } else if (id == "VIN") {
-               return VI;
-       } else if (id == "Lrs") {
-               return BSL;
-       } else if (id == "Rrs") {
-               return BSR;
-       } else if (id == "DBOX") {
-               return MOTION_DATA;
-       } else if (id == "FSKSync") {
-               return SYNC_SIGNAL;
-       } else if (id == "SLVS") {
-               return SIGN_LANGUAGE;
+       transform(id.begin(), id.end(), id.begin(), ::tolower);
+
+       if (id == "l") {
+               return Channel::LEFT;
+       } else if (id == "r") {
+               return Channel::RIGHT;
+       } else if (id == "c") {
+               return Channel::CENTRE;
+       } else if (id == "lfe") {
+               return Channel::LFE;
+       } else if (id == "ls" || id == "lss") {
+               return Channel::LS;
+       } else if (id == "rs" || id == "rss") {
+               return Channel::RS;
+       } else if (id == "hi") {
+               return Channel::HI;
+       } else if (id == "vin") {
+               return Channel::VI;
+       } else if (id == "lrs") {
+               return Channel::BSL;
+       } else if (id == "rrs") {
+               return Channel::BSR;
+       } else if (id == "dbox") {
+               return Channel::MOTION_DATA;
+       } else if (id == "sync" || id == "fsksync") {
+               return Channel::SYNC_SIGNAL;
+       } else if (id == "slvs") {
+               return Channel::SIGN_LANGUAGE;
        }
 
        throw UnknownChannelIdError (id);
@@ -723,31 +598,31 @@ string
 dcp::channel_to_mca_id (Channel c, MCASoundField field)
 {
        switch (c) {
-       case LEFT:
+       case Channel::LEFT:
                return "L";
-       case RIGHT:
+       case Channel::RIGHT:
                return "R";
-       case CENTRE:
+       case Channel::CENTRE:
                return "C";
-       case LFE:
+       case Channel::LFE:
                return "LFE";
-       case LS:
-               return field == FIVE_POINT_ONE ? "Ls" : "Lss";
-       case RS:
-               return field == FIVE_POINT_ONE ? "Rs" : "Rss";
-       case HI:
+       case Channel::LS:
+               return field == MCASoundField::FIVE_POINT_ONE ? "Ls" : "Lss";
+       case Channel::RS:
+               return field == MCASoundField::FIVE_POINT_ONE ? "Rs" : "Rss";
+       case Channel::HI:
                return "HI";
-       case VI:
+       case Channel::VI:
                return "VIN";
-       case BSL:
+       case Channel::BSL:
                return "Lrs";
-       case BSR:
+       case Channel::BSR:
                return "Rrs";
-       case MOTION_DATA:
+       case Channel::MOTION_DATA:
                return "DBOX";
-       case SYNC_SIGNAL:
+       case Channel::SYNC_SIGNAL:
                return "FSKSync";
-       case SIGN_LANGUAGE:
+       case Channel::SIGN_LANGUAGE:
                return "SLVS";
        default:
                break;
@@ -761,31 +636,31 @@ string
 dcp::channel_to_mca_name (Channel c, MCASoundField field)
 {
        switch (c) {
-       case LEFT:
+       case Channel::LEFT:
                return "Left";
-       case RIGHT:
+       case Channel::RIGHT:
                return "Right";
-       case CENTRE:
+       case Channel::CENTRE:
                return "Center";
-       case LFE:
+       case Channel::LFE:
                return "LFE";
-       case LS:
-               return field == FIVE_POINT_ONE ? "Left Surround" : "Left Side Surround";
-       case RS:
-               return field == FIVE_POINT_ONE ? "Right Surround" : "Right Side Surround";
-       case HI:
+       case Channel::LS:
+               return field == MCASoundField::FIVE_POINT_ONE ? "Left Surround" : "Left Side Surround";
+       case Channel::RS:
+               return field == MCASoundField::FIVE_POINT_ONE ? "Right Surround" : "Right Side Surround";
+       case Channel::HI:
                return "Hearing Impaired";
-       case VI:
+       case Channel::VI:
                return "Visually Impaired-Narrative";
-       case BSL:
+       case Channel::BSL:
                return "Left Rear Surround";
-       case BSR:
+       case Channel::BSR:
                return "Right Rear Surround";
-       case MOTION_DATA:
+       case Channel::MOTION_DATA:
                return "D-BOX Motion Code Primary Stream";
-       case SYNC_SIGNAL:
+       case Channel::SYNC_SIGNAL:
                return "FSK Sync";
-       case SIGN_LANGUAGE:
+       case Channel::SIGN_LANGUAGE:
                return "Sign Language Video Stream";
        default:
                break;
@@ -807,31 +682,31 @@ dcp::channel_to_mca_universal_label (Channel c, MCASoundField field, ASDCP::Dict
        };
 
        switch (c) {
-       case LEFT:
+       case Channel::LEFT:
                return dict->ul(ASDCP::MDD_DCAudioChannel_L);
-       case RIGHT:
+       case Channel::RIGHT:
                return dict->ul(ASDCP::MDD_DCAudioChannel_R);
-       case CENTRE:
+       case Channel::CENTRE:
                return dict->ul(ASDCP::MDD_DCAudioChannel_C);
-       case LFE:
+       case Channel::LFE:
                return dict->ul(ASDCP::MDD_DCAudioChannel_LFE);
-       case LS:
-               return dict->ul(field == FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Ls : ASDCP::MDD_DCAudioChannel_Lss);
-       case RS:
-               return dict->ul(field == FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Rs : ASDCP::MDD_DCAudioChannel_Rss);
-       case HI:
+       case Channel::LS:
+               return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Ls : ASDCP::MDD_DCAudioChannel_Lss);
+       case Channel::RS:
+               return dict->ul(field == MCASoundField::FIVE_POINT_ONE ? ASDCP::MDD_DCAudioChannel_Rs : ASDCP::MDD_DCAudioChannel_Rss);
+       case Channel::HI:
                return dict->ul(ASDCP::MDD_DCAudioChannel_HI);
-       case VI:
+       case Channel::VI:
                return dict->ul(ASDCP::MDD_DCAudioChannel_VIN);
-       case BSL:
+       case Channel::BSL:
                return dict->ul(ASDCP::MDD_DCAudioChannel_Lrs);
-       case BSR:
+       case Channel::BSR:
                return dict->ul(ASDCP::MDD_DCAudioChannel_Rrs);
-       case MOTION_DATA:
+       case Channel::MOTION_DATA:
                return dict->ul(ASDCP::MDD_DBOXMotionCodePrimaryStream);
-       case SYNC_SIGNAL:
+       case Channel::SYNC_SIGNAL:
                return ASDCP::UL(sync_signal);
-       case SIGN_LANGUAGE:
+       case Channel::SIGN_LANGUAGE:
                return ASDCP::UL(sign_language);
        default:
                break;
@@ -844,20 +719,55 @@ dcp::channel_to_mca_universal_label (Channel c, MCASoundField field, ASDCP::Dict
 vector<dcp::Channel>
 dcp::used_audio_channels ()
 {
-       vector<dcp::Channel> c;
-       c.push_back (LEFT);
-       c.push_back (RIGHT);
-       c.push_back (CENTRE);
-       c.push_back (LFE);
-       c.push_back (LS);
-       c.push_back (RS);
-       c.push_back (HI);
-       c.push_back (VI);
-       c.push_back (BSL);
-       c.push_back (BSR);
-       c.push_back (MOTION_DATA);
-       c.push_back (SYNC_SIGNAL);
-       c.push_back (SIGN_LANGUAGE);
-       return c;
+       return {
+               Channel::LEFT,
+               Channel::RIGHT,
+               Channel::CENTRE,
+               Channel::LFE,
+               Channel::LS,
+               Channel::RS,
+               Channel::HI,
+               Channel::VI,
+               Channel::BSL,
+               Channel::BSR,
+               Channel::MOTION_DATA,
+               Channel::SYNC_SIGNAL,
+               Channel::SIGN_LANGUAGE
+       };
+}
+
+
+string
+dcp::formulation_to_string (dcp::Formulation formulation)
+{
+       switch (formulation) {
+       case Formulation::MODIFIED_TRANSITIONAL_1:
+               return "modified-transitional-1";
+       case Formulation::MULTIPLE_MODIFIED_TRANSITIONAL_1:
+               return "multiple-modified-transitional-1";
+       case Formulation::DCI_ANY:
+               return "dci-any";
+       case Formulation::DCI_SPECIFIC:
+               return "dci-specific";
+       }
+
+       DCP_ASSERT (false);
+}
+
+
+dcp::Formulation
+dcp::string_to_formulation (string formulation)
+{
+       if (formulation == "modified-transitional-1") {
+               return Formulation::MODIFIED_TRANSITIONAL_1;
+       } else if (formulation == "multiple-modified-transitional-1") {
+               return Formulation::MULTIPLE_MODIFIED_TRANSITIONAL_1;
+       } else if (formulation == "dci-any") {
+               return Formulation::DCI_ANY;
+       } else if (formulation == "dci-specific") {
+               return Formulation::DCI_SPECIFIC;
+       }
+
+       DCP_ASSERT (false);
 }