OcaDynamicsCurve#
This document describes the device side implementation of OcaDynamicsCurve. This class is documented in the AES70 standard as:
Dynamic compression / expansion curve.
Curve means a function that expresses the relationship of output level to input level. The dependent variable (Y) of the curve is output level; the independent variable (X) is input level.
Every curve shall be composed of (n+1) straight-line segments joined by (n) small fillets called knees. Each knee shall occur at a particular input level value called the threshold. Each segment shall be characterized by its slope.
| / | S3 / | S2 / | T1-------------T2 | / | S1 / | / | / +------------------------------------
This "drawing" shows a three-segment curve. The horizontal axis is input level, vertical axis is output level.
Algebraically, a curve shall be a function
Out = Curve( In, T[1..n-1], S[1..n], K[1..n-1] )
where n is the number of segments, and In is input level in dBr Out is output level in dBr T[1...n-1] is an array of thresholds in dBr S[1...n] is an array of slopes in dBr per dBr, i.e. unitless K[1..n] is the knee parameter, an implementation-dependant parameter that specifies the shape of the curve around the knee.
Each segment shall have a slope that expresses its ratio of output level to input level. Note that this slope is the inverse of what dynamics processors call "ratio". For example, a ratio of 2:1 shall be represented by a curve segment with slope 1/2 = 0.5.
This model can represent various kinds of audio dynamics elements (we ignore K[] in these examples):
- Compressor with ratio of 2:1 and threshold of 10dBr: n = 2 T[1] = 10 S[1] = 1 S[2] = 0.5
- Hard limiter with threshold of 18dBr: n = 2 T[1] = 18 S[1] = 1 S[2] = 0
- Upward expander with ratio of 1.5:1 and threshold of -12dBr: n = 2 T[1] = -12 S[1] = 1 S[2] = 1.5
- Downward expander (gate) with ratio of 50:1 and threshold of -45dBr: n = 2 T[1] = -45 S[1] = 50 S[2] = 1
This class, OcaDynamicsCurve, shall add two additional parameters to the basic curve mechanism.
Out = Curve( In, T[1..n-1], S[1..n], K[1..n-1] , Floor, Ceiling) where In, T[], and S[], and K[] are as defined above. Floor shall be the lowest gain (in dBr) that the dynamics element is allowed to produce. Ceiling shall be the highest gain (in dBr) that the dynamics element is allowed to produce.
To show the use of Floor and Ceiling, we revisit some of the examples above (again, K[] is ignored):
- Compressor with ratio of 2:1, threshold of 10dBr, and max gain reduction of 20dB: n = 2 T[1] = 10 S[1] = 1 S[2] = 0.5 Floor = -20 Ceiling = 0
- Upward expander with ratio of 1.5:1, threshold of -12dBr, and max gain boost of 4dB: n = 2 T[1] = -12 S[1] = 1 S[2] = 1.5 Floor = 0 Ceiling = 4.0
More complex dynamics curves may be modeled by using more segments (n > 2).
Overview#
- ClassID: 1.1.1.16
- Header:
aes70/device/OcaDynamicsCurve.hpp
- Namespace:
aes70::device
- Inheritance: aes70::device::OcaActuator, aes70::device::OcaWorker, aes70::device::OcaRoot, aes70::device::object
Class Declaration#
The device side implementation has the following signature.
namespace aes70::device
{
template <class Implementation>
class OcaDynamicsCurve : public OcaActuator<Implementation>
{
};
}
The template argument Implementation
may implement the following methods.
Note
The signatures given here are just one possibility and that most methods are optional. Implement only those methods which make sense in the context of your device. See the documentation in Implementing AES70 Classes for more details.
class MyOcaDynamicsCurveImplementation
{
// Methods defined by OcaDynamicsCurve
std::tuple<OcaUint8,OcaUint8,OcaUint8> GetNSegments();
void SetNSegments(OcaUint8 Slope);
std::tuple<OcaDBr,OcaDBz,OcaDBz> GetThreshold();
void SetThresholds(OcaList<OcaDBr> Threshold);
std::tuple<OcaList<OcaFloat32>,OcaList<OcaFloat32>,OcaList<OcaFloat32>> GetSlopes();
void SetSlopes(OcaList<OcaFloat32> slope);
std::tuple<OcaList<OcaFloat32>,OcaList<OcaFloat32>,OcaList<OcaFloat32>> GetKneeParameters();
void SetKneeParameters(OcaList<OcaFloat32> Parameters);
std::tuple<OcaDB,OcaDB,OcaDB> GetDynamicGainCeiling();
void SetDynamicGainCeiling(OcaDB gain);
std::tuple<OcaDB,OcaDB,OcaDB> GetDynamicGainFloor();
void SetDynamicGainFloor(OcaDB Gain);
void SetMultiple(OcaParameterMask Mask, OcaUint8 NSegments, OcaList<OcaDBr> Thresholds, OcaList<OcaFloat32> Slope, OcaList<OcaFloat32> KneeParameter, OcaDB DynamicGainFloor, OcaDB DynamicGainCeiling);
std::tuple<OcaList<OcaDBr>,OcaDBz,OcaDBz> GetThresholds();
// Methods defined by OcaWorker
OcaBoolean GetEnabled();
void SetEnabled(OcaBoolean enabled);
OcaPortID AddPort(OcaString Name, OcaIODirection Mode);
void DeletePort(OcaPortID ID);
OcaList<OcaPort> GetPorts();
OcaString GetPortName(OcaPortID PortID);
void SetPortName(OcaPortID ID, OcaString Name);
OcaString GetLabel();
void SetLabel(OcaString label);
OcaONo GetOwner();
OcaTimeInterval GetLatency();
void SetLatency(OcaTimeInterval latency);
std::tuple<OcaRolePath,OcaONoPath> GetPath();
OcaMap<OcaPortID, OcaPortClockMapEntry> GetPortClockMap();
void SetPortClockMap(OcaMap<OcaPortID, OcaPortClockMapEntry> Map);
OcaPortClockMapEntry GetPortClockMapEntry(OcaPortID ID);
void SetPortClockMapEntry(OcaPortID PortID, OcaPortClockMapEntry Entry);
void DeletePortClockMapEntry(OcaPortID ID);
// Methods defined by OcaRoot
OcaBoolean GetLockable();
void SetLockNoReadWrite();
void Unlock();
OcaString GetRole();
void SetLockNoWrite();
OcaLockState GetLockState();
};