Skip to content

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 of make_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 using make_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);
};