Home

Awesome

<h1 align="center"> <img src="docs/logo.png" alt="avendish"> <br/> </h>

declarative polyamorous cross-system intermedia objects

C++ Sponsor License Platforms GitHub Workflow Status

A zero-cost, compile-time, reflection-based, pure C++ solution to the quadratic glue MxN problem:

Read the documentation to get started !

The original blog post contains the motivation and some implementation details.

If you use this work in an academic setting, please cite the paper:

@inproceedings{celerier2022rage,
  title={{Rage Against The Glue: Beyond Run-Time Media Frameworks with Modern C++}},
  author={Celerier, Jean-Micha\"el},
  booktitle={Proceedings of the International Computer Music Conference (ICMC)},
  year={2022},
  address={Limerick, Ireland}
}

Features

This library is a proof-of-concept (based on the Boost.PFR library and a couple of tricks), which showcases how with what little reflection modern C++ provides, it is possible to:

And some more advanced features:

Design

Unlike many other reflection solutions in C++, this library has two properties:

The current catch is that types being introspected must be aggregates. This is not a very hard restriction in practice and allows for plenty of useful things.

The API is not as clean as it could be - the end-goal would be to have the meta-class and compile-time programming papers merged in the C++ standard in order to remove the remaining little boilerplate there is and open the use-cases & lift various dumb restrictions:

Committee if you hear us :)

Dependencies

Examples

See https://github.com/celtera/avendish/tree/main/examples for a list of examples.

Here is an example of various Max/MSP & PureData objects generated from the examples folder ; the patches are available in https://github.com/celtera/avendish/tree/main/docs.

Max/MSP example

PureData example

Usage

The simplest way to get started is to take a look at the examples, and clone the template repository.

A recent enough clang version is provided for all platforms (check the CI scripts in .github/workflows/cmake).

Audio processor

A most basic avendish audio processor would look like this:

#pragma once
struct Distortion
{
  static consteval auto name() { return "Distortion"; }
  static consteval auto c_name() { return "disto"; }
  static consteval auto uuid() { return "dd4dd880-d525-44fb-9773-325c87b235c0"; }

  struct {
    struct {
      static consteval auto name() { return "Preamp"; }
      static consteval auto control() {
        struct {
          const float min = 0.001;
          const float max = 1000.;
          const float init = 1.;
        } c; return c;
      }

      float value{0.5};
    } preamp;

    struct {
      static consteval auto name() { return "Volume"; }
      float value{1.0};
    } volume;
  } inputs;

  void operator()(double** in, double** out, int frames)
  {
    const double preamp = inputs.preamp.value;
    const double volume = inputs.volume.value;

    for (int c = 0; c < channels; c++)
      for (int i = 0; i < frames; i++)
        out[c][i] = volume * std::tanh(in[c][i] * preamp);
  }
};
}

It will create for instance an audio plug-in with two parameters.

The next example will create a PureData object which:

#pragma once
struct Addition
{
  static consteval auto name() { return "Addition"; }
  static consteval auto c_name() { return "avnd_addition"; }
  static consteval auto uuid() { return "36427eb1-b5f4-4735-a383-6164cb9b2572"; }

  struct {
    struct { float value; } a;
    struct { float value; } b;
  } inputs;

  struct {
    struct { float value; } out;
  } outputs;

  struct {
    struct {
      static consteval auto name() { return "member"; }
      static consteval auto func() { return &Messages::bamboozle; }
    } member;
  } messages;

  void bamboozle(float x, float y, const char* str)
  {
    inputs.a.value = x;
    inputs.b.value = y;
  }

  static constexpr std::tuple initialize{
      [] (Init& self, float a) { std::cout << "A: " << a << std::endl; }
    , [] (Init& self, const char* a, const char* b) { std::cout << "B: " << a << b << std::endl; }
  };

  void operator()()
  {
    outputs.out.value = inputs.a.value + inputs.b.value;
  }
};

A small library of helpers types and macros is provided to simplify the most common use-cases but is in no way mandatory.

Advanced features

As this library currently focuses on the "concept" of an audio effect processor or synthesizer, it provides various amenities tailored for that use case:

void operator()(double** in, double** out, int frames) { /* my audio code */ }

float operator()(float in) { /* my audio code */ }

... etc ...

If a mono processor is written, the library will wrap it automatically in the case of a multichannel requirement from the host.

Past travels

Future directions (todo!)

Future directions (done!)

Licensing

The library is licensed under GPLv3+commercial. The concepts are licensed as permissively as possible (any of Boost license, public domain, BSD0, CC0, you name it) as it's mainly the concepts which enable interoperability. The end goal being that people who make, e.g. plug-in specs provide their own generators based on these concepts, to make sure that we have a thriving interoperable ecosystem.