Static Devices#
The APIs in the aes70::static_device
namespace are designed to implement AES70 devices which do not require any dynamic memory allocation.
The main two APIs are
auto aes70::static_device::make_block(const char *Role, auto ... args)
. Creates a static block given a set of AES70 objects.auto aes70::static_device::make_root(...)
A special variant ofmake_block
for the root block (the top level block in an AES70 device).auto aes70::static_device::make_device(auto &root)
Creates the device structure. The first argument is the root block. Optionally, the second argument can be the manager objects to be added in this device.
Using make_block
#
Conceptually make_block
works similarly to std::make_tuple
. It returns a instance of aes70::static_device::block<TN...>
where the template arguments are derived from the arguments passed to make_block
.
Arguments#
const char *Role
- The Role name of this block.auto &&... members
- The block members.
Member objects are expected to be AES70 objects or arrays of AES70 objects (see below).
make_block
treats arguments depending on their type:
- Member objects passed by value or rvalue reference are stored inside the returned block.
- Member objects passed by lvalue reference are stored as reference in the returned block.
Example:
In the following example, gain1
and gain2
are stored as references in block
, while another
object called "Gain3"
is instantiated inside of the block itself.
MyGain gain1("Gain1"), gain2("Gain1");
auto block = aes70::static_device::make_block(
"Channel1",
gain1,
gain2,
MyGain("Gain3")
);
Storing objects as references inside of blocks can be useful to access more easily, e.g. to trigger property updates.
Example:
std::array<MyLevel, 4> input_levels = {...};
MyGain gain1("Gain1"), gain2("Gain1");
auto block = aes70::static_device::make_block(
"Inputs",
aes70::static_device::make_block("1", input_levels[0]),
aes70::static_device::make_block("2", input_levels[1]),
aes70::static_device::make_block("3", input_levels[2]),
aes70::static_device::make_block("4", input_levels[3]),
);
static void update_levels() {
for (size_t i = 0; i < 4; i++)
{
input_levels[i].update_level();
}
}
Handling of std::array
and std::span
#
Additonally, make_block
supports arguments of type std::array
or std::span
with static extent.
This can be used to efficiently reference a list of objects of the same type
inside of a block.
Example:
std::array<MyLevel, 4> input_levels = {...};
auto block = aes70::static_device::make_block(
"Inputs",
aes70::static_device::make_block("Levels", input_levels)
);
static void update_levels() {
for (size_t i = 0; i < 4; i++)
{
input_levels[i].update_level();
}
}
Accessing objects#
aes70::static_device::block
has a method called get<N>
which can be used to access
members in the block.
template <typename ... N>
class block
{
// Returns the block member at position Index.
template <size_t Index>
auto &get();
};
Note that this method returns a reference to the stored member. If an argument
of type std::array
passed to make_block
, the member is that array.
Using make_root
#
make_root
creates a special block which can be used as the top level root block.
Example:
static auto make_input_channel(const char *Role, size_t index) {
return aes70::static_device::make_block(
Role,
MyGain("Gain", index),
input_levels[index]
);
}
auto root = aes70::static_device::make_root(
make_input_channel("1", 0),
make_input_channel("2", 1),
make_input_channel("3", 2),
make_input_channel("4", 3),
);
Using make_device
#
make_device
can be used to create the static device structure given a root block.
Additonal arguments are optional, they can be used to define custom manager objects
or define custom object numbering scheme.
namespace aes70::static_device
{
auto make_device(auto &root);
auto make_device(auto &root, auto &managers);
auto make_device(auto &root, auto &managers, auto object_number_map);
}
Arguments:
root
: The root block. Must be created usingmake_root
.- (optional)
managers
: A reference to an object containing managers. - (optional)
object_number_map
: An object which can be used to define custom object numbering.
Before a device can be used in a device it is required to call the init()
method on the device.
This method will assign object numbers to all objects in the tree and initialize other internal
structures.
Defining manager objects#
make_device
allows defining custom managers in a device. Manager objects are singletons, each
manager can only exist once.
The only manager which is implemented automatically is the SubscriptionManager
.
Example:
auto root = aes70::static_device::make_root(...);
struct {
MyDeviceManager DeviceManager;
} managers;
auto device = aes70::static_device::make_device(root, managers);
Note
The AES70 standard requires devices to implement the DeviceManager. If is therefore recommended
to always define a device manager. See the examples
directory for example implementations.
Customizing object numbers#
By default the static device implementation will assign object numbers to devices in the following way:
- The root block will always have object numer
100
. This is mandated by the standard. - Objects inside of the root block will be assigned an index in depth-first order.
- Each object will then be assigned the object number
4096 + index
.
Object numbers of objects in a static device can be customized. This is done by implementing a object number map which has the following interface:
class OBJECT_NUMBER_MAP
{
// Returns the offset for a given object number.
ptrdiff_t object_number_to_offset(uint32_t object_number);
// Returns the object number of an object at the given index.
uint32_t offset_to_object_number(ptrdiff_t offset);
};