Library Design

The API of the libadm library is probably not self-explanatory if some of the concepts are not known. This sections aims at filling the gaps.

Named types

The libadm library makes an extensive use of so called named types. Thus, it is essential to understand named types to understand the design of the library. So we begin with a short introduction into named types. For more information on the implementation details of named types please refer to the blog post series on FluentCpp.

The idea of named types is to not use standard types directly but instead wrap them into a class. This approach has several advantages. Most importantly it makes the code safer and more expressive. To illustrate this have a look at the following snippet, which creates an adm::AudioContent object.

auto speechContentDe = AudioContent::create(AudioContentName("Speech"),
                                            AudioContentLanguage("de"));

It is obvious, that Speech is the name of the adm::AudioContent and de is the language. Yet still you can compare these types to a std::string like this:

auto myContentName = AudioContentName("Speech");
if (myContentName == "Speech") {
  std::cout << "Name of Content is Speech" << std::endl;
}

If for whatever reason it is really necessary to get the underlying type we can get it using the get method of the NamedTypes. But this should usually be avoided.

std::string myContentName = AudioContentName("Speech").get();

As we don’t want to manually write a class for each type, named types are declared using the templated class adm::detail::NamedType. This makes declaring a new named type quite simple. The declaration for the adm::AudioContentName for example looks like this.

struct AudioContentNameTag {};
using AudioContentName = detail::NamedType<std::string, AudioContentNameTag>;

But the named types offer even more functionality. We can add a validator to it. Some basic validators are already implemented. E. g. the adm::Importance within the ADM can only have values between 0 and 10. To achieve this we add a adm::detail::RangeValidator to the type declaration.

struct ImportanceTag {};
using Importance = detail::NamedType<int, ImportanceTag, detail::RangeValidator<0, 10>>;

Basic structure

The libadm library is (for now) a quite low level library. Every ADM element has a class representation (either an ordinary class or a named type). Every class or named type is named exactly the same as in the ADM. The main ADM elements (see following list) are then managed by an adm::Document.

Note

At the moment there are still some sub-elements missing. Please refer to the documentation of the main ADM elements for a list of supported/unsupported sub-elements.

The adm::Document and the main ADM elements always have to be std::shared_ptr<>. This is enforced by making the constructors private and adding static create functions to each class, which return a std::shared_ptr<>.

Note

An ADM element can only belong to one adm::Document!

Once added to an adm::Document they cannot be added to another one. Trying to do so will result in a std::runtime_error. If you really want to move or copy an ADM element to another adm::Document the adm::Document::move() and adm::Document::copy() functions of the adm::Document have to be used.

As you have maybe noticed the AudioBlockFormats are not part of the previous list of main ADM Elements. That’s because they are more like a special attribute of the adm::AudioChannelFormat. As the main ADM elements they also can only be created as std::shared_ptr<> s, but instead of the adm::Document they are managed by the adm::AudioChannelFormat they belong to. The same principles as for the main ADM elements and the adm::Document apply here. An AudioBlockFormat can only belong to one adm::AudioChannelFormat and if you want to move or copy it you have to use the corresponding functions of the adm::AudioChannelFormat.

References

References between the basic ADM elements can be established using the addReference or addReferences methods. Trying to establish a reference between two ADM elements which belong to different adm::Document results in a std::runtime_error. Adding an ADM element to an adm::Document will automatically add the referenced ADM elements too.

Overloaded/templated methods whenever possible

As we use classes or named types everywhere, it is quite straight forward to overload or use templated functions. Sub-elements or attributes of an ADM element can all be accessed using the following set of functions:

Function

Explanation

Parameter get<Parameter>();

Templated getter method

void set(Parameter parameter);

Overloaded setter method

bool has<Parameter>();

Returns true if the ADM parameter is set or has a default value.

void unset<Parameter>();

Removes the ADM parameter if it is optional or resets it to the default value if there is one.

bool isDefault<Parameter>();

Returns true if the current ADM parameter is the default value.

To illustrate the usage here is a simple example which uses them.

JumpPosition jumpPosition;

if(jumpPosition.has<InterpolationLength>() == true &&
   jumpPosition.isDefault<InterpolationLength>() == true) {
  std::cout << "JumpPositon has a default value for InterpolationLength: "
            << jumpPosition.get<InterpolationLength>() << std::endl;
}

jumpPosition.set(InterpolationLength(1.5f));
if(jumpPosition.has<InterpolationLength>() == true &&
   jumpPosition.isDefault<InterpolationLength>() == false) {
  std::cout << "InterpolationLength is now set to a custom value: "
            << jumpPosition.get<InterpolationLength>() << std::endl;
}

For more detail, see Element API.

Constructors with optional arguments in arbitrary order

The constructors (or the create functions in case of the main ADM elements) also make use of the named types. Using some black template magic they support optional arguments in arbitrary order. So let us revisit our first named type example.

auto speechContentDe = AudioContent::create(AudioContentName("Speech"),
                                            AudioContentLanguage("de"));

We can simply reorder adm::AudioContentName and adm::AudioContentLanguage and have the same result.

auto speechContentDe = AudioContent::create(AudioContentLanguage("de"),
                                            AudioContentName("Speech"));

Reading/writing ADM data

Parsing ADM data is as easy as it gets. You just have to include the file adm/parse.hpp and use one of the adm::parseXml() functions. You can either pass an std::istream

std::istream myAdmDataStream;
// Add XML data to stream ...
auto admDocument = adm::parseXml(myAdmDataStream);

or use the convenience function and pass the name of the input file as a std::string

std::string myFilename("./my_adm_file.xml");
auto admDocument = adm::parseXml(myFilename);

The same applies for writing an adm::Document to a file or stream. You just have to include the file adm/write.hpp and use one of the adm::writeXml() functions. You can either pass an std::ostream

auto admDocument = adm::Document::create();
// Add ADM elements ...
std::ostream xmlStream(...);
adm::writeXml(xmlStream, admDocument);

or use the convenience function and pass the name of the output file as a std::string

auto admDocument = adm::Document::create();
// Add ADM elements ...
adm::writeXml("outFilename.xml", admDocument);