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 |
---|---|
|
Templated getter method |
|
Overloaded setter method |
|
Returns true if the ADM parameter is set or has a default value. |
|
Removes the ADM parameter if it is optional or resets it to the default value if there is one. |
|
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);