Brickworks > API documentation

Brickworks API

Brickworks is a music DSP toolkit that offers a solid foundation for creating and enhancing audio engines on any platform.

Technical background

Brickworks is a header-only library written in plain C99 and only requires:

The API user is given some flexibility in supplying such definitions (see bw_common).

The code is organized in separate modules which are as independent as possible but still may require (i.e., depend on) other modules. Each module consists of an header file having filename bw_module.h.

Brickworks is not meant to be built and used as a static or shared library. You should rather just use the needed module files to develop your software.

Both individual modules and the whole library are versioned according to the Semantic Versioning Specification 2.0.0. Module dependencies are considered part of the API, therefore if a dependency is removed that would lead to MINOR version change, and if one is added that would cause a MAJOR version change.

Unless otherwise stated, the C API follows the following conventions:

C++ wrappers

Optional C++ wrappers are supplied in most modules. The API they expose is a wide subset of the C API which is targeted at the most common use cases, therefore trading some flexibility for ease of use. The C API is still accesible from C++.

These wrappers also require:

Unless otherwise stated, the same conventions of the C API apply. Also:

Modules

Foundation

These modules are required by most, if not all, other modules and just contain a few basic essential definitions. They provide a certain degree of consistency and adaptability to the rest of the code.

Utility

Utility modules contain heterogeneous code that is repeatedly used by applications and other modules.

DSP

These modules implement signal processing blocks (not necessarily audio-related).

Their APIs are not 100% uniform, but they tend to follow a common pattern described here. Varations to this pattern are indicated in the individual module's documentation.

Each signal processing block conceptually consists of:

Data types

Signal inputs and outputs are not explicitly represented by any data structure, but you rather see them as arguments or return values of processing functions. Typically, signal data is PCM-encoded as (buffers of) floats, whose values usually lie in the [-1.f, 1.f] range (these extremes correspond to the 0 dBFS clipping points — belonging to this range is not a strict requirement but values MUST be finite), and uniformly-sampled at a user-supplied sample rate, which is common to all signal inputs and outputs of a block instance. Signals that vary at such sample rate are also known as audio-rate signals.

Parameters, on the other hand, can have different types and ranges and are asynchronously updated at a variable rate, also known as control rate, that is lower than the sample rate.

Less commonly, there can also be control-rate signals. These are analogous to parameters except they are passed as arguments or returned by processing functions.

Coefficients are sets of heterogeneous values specific to a module type that are computed and kept up to date by various functions at control and/or audio rate on the basis of input parameter values. They are stored into a data structure that is opaque to the API user.

If it is supposed to have one, a block instance can be uniquely characterized by a data structure containing its internal state, which is also opaque to the API user and indirectly updated by reset and processing functions.

Data-wise, a block instance can be composed of a set of coefficients and its internal state. A given set of coefficients, however, can be associated with many block instances. In other words, a block instance is in a 1:1 relationship with its internal state (if it has one), while coefficients are in a 1:N relationship with states/instances. When a set of coefficients is shared by more instances, these act like they have all the same input parameter settings (this can be particularly useful for multichannel audio and other parallel structures).

In each module you may find:

In C++ wrappers much of this arrangement is hidden from the API user, who is instead presented with class templates implementing a variable number of instances sharing common coefficients.

Functions

The APIs of these modules superficially resemble audio plugin APIs, while being based on a common underlying computational and behavioral model influenced by finite-state machines.

API functions make coefficient and internal state data structures transition their states, which are ordered. Each function can be called on coefficients/states that have reached at least a given state.

The rest of this section will describe the API of an archetypical bw_module.

Uninitialized state

Before any function call, coefficients and states are in an uninitialized state where their content is undefined and invalid.

Initialize coefficients

You may find a bw_module_init() function that resets input parameters to their default values, possibly taking initialization-time variables as input arguments. This can be called on a set of coefficients in any state, including the uninitialized one, and transitions it to the initialized state.

Set the sample rate in coefficients

You may find a bw_module_set_sample_rate() function that sets the given sample rate value (which MUST be finite and positive) in the supplied set of coefficients, which MUST be at least in the initialized state and which will transition to a "sample-rate-set" state.

If this function is not present, coefficients in the initialized state automatically reach the "sample-rate-set" state.

Associate memory chunks to an internal state

Some modules require the API user to provide a chunk of contiguous memory to be used as part of the internal state of an instance. This arrangement avoids the need to directly rely on dynamic memory allocation and puts the burdens and the honours of managing memory in the hands of the API user. The size of the required memory chunk is typically dependent on the sample rate and eventual initialization-time variables.

For this purpose, you may find bw_module_mem_req() and bw_module_mem_set() functions which, respectively, retrieve the size of the required memory chunk and associate a memory chunk to a given internal state.

Both need the given set of coefficients to be at least in the "sample-rate-set" state, while the latter does not impose restrictions on the given internal state (it can be uninitialized) and transitions it into the "memory-chunk-set" state.

If these functions are not present, uninitialized internal states automatically reach the "memory-chunk-set" state.

Reset coefficients

You may find a bw_module_reset_coeffs() function that "resets" coefficients, which MUST be at least in the "sample-rate-set" state, and transititions them to the "reset" state.

As said, coefficients are computed on the basis of input parameters. As they may be subject to smoothing or other dynamic update patterns, their values may evolve over time even if input parameter values stay constant from a certain moment on. However, they are required to converge towards fixed values given constant input parameter values and sample rate. This function forces coefficients to assume such target values, and thus depends on the current input parameter values.

If this function is not present, coefficients in the "sample-rate-set" state automatically reach the "reset" state.

Reset state

You may find bw_module_reset() and bw_module_reset_multi() functions that "reset" internal states, which MUST be at least in the ""memory-chunk-set" state, and transition them to the "reset" state. Also, the associated coefficients MUST be at least in the "reset" state.

Internal states are reset according to current input parameter values, coefficients, and potentially supplied initial input values.

If these functions are not present, internal states in the "memory-chunk-set" state automatically reach the "reset" state.

Update coefficients and process signals

You may find:

In case input parameters are allowed to change, they MUST be continuously updated both at control and audio rate. Unless that is handled already by processing functions, you are responsible for invoking the relevant functions as needed. In this sense, bw_module_process_ctrl() is analogous to bw_module_update_coeffs_ctrl() (i.e., it MUST be called at control rate if present and parameters can change and not using bw_module_process() or bw_module_process_multi()).

Internal states, instead, are only updated by processing functions.

When using any of these functions, both coefficients and internal states MUST be at least in the "reset" state.

Get and set parameters

You may find one or more bw_module_set_parameter() and bw_module_get_parameter() functions that, respectively, set an input parameter value and get an output parameter value.

When using these functions, coefficients MUST be at least in the initialized state while internal states MUST be at least in the "reset" state.

Effects
Distortion
Dynamics
Modulation
Filter
Time-based
Degradation
Generators
Oscillators
Other
Utilities
Mixing
Tools

Glossary

Definitions in this glossary are not meant to be general but only valid in the context of using the Brickworks API.

No side effects
A function has no side effects if it does not affect any external state (except data referenced by arguments).
Reentrant function
A function that does not read external variable data (beyond input data).
RT-safe function
A function that has finite and deterministic execution time (e.g., it doesn't block or loop undefinitely), with at worst linear time complexity w.r.t. input data.
Thread-safe function
A function that is safe to use concurrently by multiple threads operating on the same input and external data.