Documentation

LevelAPI2 & LevelShow2
R128 Loudness C API Documentation

LevelAPI2 is a high-performance C dynamic library for loudness measurement and normalization to EBU R128 and ATSC A/85 (CALM Act) broadcast standards. This reference covers the full API, integration workflow, and all function signatures. The realtime adjuster, which holds a live signal on a loudness target in a single pass, has its own chapter. LevelShow2, the bundled DirectShow filter for Windows pipelines, is documented in the second half.

01 Introduction

LevelAPI2 and LevelShow2 are built to support the transition from peak normalization to loudness normalization, one of the most significant changes in broadcast audio in decades. This shift began when the ITU published BS.1770 (LKFS/LUFS loudness metering) in 2006. The European Broadcast Union built on this foundation with EBU R128, released in 2010, which became the global broadcast loudness standard.

LevelAPI2 is the direct evolution of the software used by the EBU PLOUD working group during the creation of R128. It is maintained by tlemon.com.

The library interface consists entirely of C functions exposed through a dynamic library (.dll on Windows, .so on Linux). This makes it callable from any language with standard C FFI support: C, C++, Java, LUA, Python, Rust, Go, .NET, Delphi, and others.

02 System Requirements

  • Windows 7 or later (x86 / x64)
  • Linux: built on CentOS 7 (x64); custom builds available for other distributions
  • System memory: 512 MB minimum
  • Disk space: approximately 10 MB

The Linux .so build depends on standard shared libraries: libcurl.so.4, librt.so.1, libdl.so.2, libpthread.so.0, libm.so.6, libc.so.6. All are present in any standard CentOS 7 / RHEL / Fedora environment.

03 Upgrading from LevelAPI v1

If you are migrating from the original LevelAPI, note the following breaking changes:

Renamed functions

v1v2
Analyse()AnalyseInterleaved()
Adjust()AdjustInterleaved()
GetNeedLimiting()GetLimitingNeeded()
GetLimitThresholddB()GetLimiterThreshold()
SetNeedLimiting()SetLimiterEnable()
SetLimiterThresholdEffective()SetLimiterThresholdAdjust()
SetLimitThresholddB()SetLimiterThreshold()
IsAdjustLevelLowered()Removed. Use GetLevel(AdjustLevel_NoPeak) vs GetLevel(AdjustLevel)

Renamed enum values

v1v2
Max_MLU_Max_M
Max_SLU_Max_S
Max_M_FSLUFS_Max_M
Max_S_FSLUFS_Max_S
LRA_LowLRA_Range_Low
LRA_HighLRA_Range_High
TruePeakHP / TruePeakLPTruePeak (high precision only)
PSR_MinPSR

Behaviour changes

  • All functions that return a boolean now return 1 (true) on success and 0 (false) on failure. Previously this was inconsistent.
  • PPM and RMS meters are disabled by default. Enable with SetPPMEnable(true) and SetRMSEnable(true).
  • TruePeak HP and LP have been merged into a single high-precision TruePeak.

04 Usage Guide

Implementing LevelAPI2 follows a consistent pattern regardless of language or platform. The steps below describe the complete workflow from loading the library to retrieving results.

  1. Load the dynamic library at runtime

    Use your platform's dynamic linking API (dlopen on Linux, LoadLibrary on Windows) to load the library file. A helper macro LevelAPI2_LoadDLL(path, &handles) is provided in the header to retrieve all function pointers in one call.

  2. Validate your certificate

    Call SetCertificate(cert_string, length) as the first call after loading. This validates your license. If it returns false, call GetErrorString() to retrieve the failure reason. The certificate string should be embedded or loaded from a secure location in your application.

  3. Create a handle

    Call Create_LevelAPI2(channels, samplerate, bitdepth) to create an instance. Every subsequent function call requires this handle as its first argument. You can create multiple handles to measure multiple sources simultaneously. Always call Destroy_LevelAPI2(handle) when done.

  4. Configure measurement standard

    For EBU R128 compliance, call SetPresetEBU_R128(handle). For ATSC A/85, call SetPresetATSC(handle). These convenience functions set calibration, relative gate, dialog gate, target type and level in one call. You can then override individual settings using the Advanced Settings functions if needed.

  5. Feed audio samples

    Call AnalyseInterleaved(handle, samples, numframes) with your interleaved float buffer. The numframes argument is in sample frames: for a stereo buffer of 512 samples, numframes = 256. For best performance, pass blocks of at least 256 frames. Maximum is 16 × 1024 = 16384 frames per call. For file-based workflows, use AnalyseFile(handle, filepath) directly.

  6. Read meter levels

    At any point after analysis, call GetLevel(handle, meterType, channel) using values from the MeterTypes enum. Pass -1 as channel to get the combined result across all channels. Key values: LU, LRA, TruePeak, LU_Max_M, LU_Max_S.

  7. Normalize the audio

    After analysis is complete, call AdjustInterleaved(handle, samples, numframes) to apply gain correction to your sample buffer. If using the same handle for both steps, the required adjustment level is already known. If using a separate handle for adjustment, first call SetAdjustLevel(handle, dB) with the value from GetAdjustLevel() or GetLevel(AdjustLevel). The built-in peak limiter activates automatically when needed.

Minimal C example

C
#include "LevelAPI2_dynlib.h"

// 1. Load library
struct LevelAPI2_FunctionHandles api;
void* lib = LevelAPI2_LoadDLL(library_path, &api);

// 2. Validate certificate
api.SetCertificate(cert_string, strlen(cert_string));

// 3. Create handle
LevelAPI2Ptr h = api.Create_LevelAPI2(channels, samplerate, bitdepth);

// 4. Configure for EBU R128
api.SetPresetEBU_R128(h);

// 5. Feed samples (interleaved float buffer)
//    numframes = total_samples / channels
api.AnalyseInterleaved(h, samples, numframes);

// 6. Read results
float lu       = api.GetLevel(h, LU,       -1);
float lra      = api.GetLevel(h, LRA,      -1);
float truepeak = api.GetLevel(h, TruePeak, -1);

// 7. Normalize
api.AdjustInterleaved(h, samples, numframes);

// Cleanup
api.Destroy_LevelAPI2(h);
LevelAPI2_UnloadDLL(lib);

05 Constants & Enums

DataRanges

ConstantValueDescription
max_channels16Maximum number of audio channels
max_frames16384Maximum sample frames per AnalyseInterleaved call (16 × 1024)

SurroundOrders

ValueChannel Order
SMTPE_ITU_AC3L, R, C, Lfe, Ls, Rs
FilmDolbyDigitalL, C, R, Ls, Rs, Lfe
DTS_ProControlL, R, Ls, Rs, C, Lfe

LoudnessStandards

Used as the standard argument in SetPresetStandard(). Each value names one loudness standard, and one variant of it where a single document defines more than one recipe. The parameters each value applies are in section 11.

The numeric values are permanent: they are never renumbered and never reused, and each family has its own block of 100 so that one family can grow without moving any other value. A build that does not know a value returns false from SetPresetStandard() and changes nothing, so a version mismatch fails visibly instead of measuring to the wrong practice.

ConstantValueStandard
LEVELAPI2_STANDARD_NONE0No standard selected
LEVELAPI2_STANDARD_EBU_R128100EBU R 128
LEVELAPI2_STANDARD_EBU_R128_2010101EBU R 128, withdrawn 2010 edition
LEVELAPI2_STANDARD_EBU_R128_S2_STREAMING102EBU R 128 s2, streaming
LEVELAPI2_STANDARD_EBU_R128_S4_CINEMA103EBU R 128 s4, cinematic
LEVELAPI2_STANDARD_ATSC_A85_LONGFORM200ATSC A/85, long form
LEVELAPI2_STANDARD_ATSC_A85_SHORTFORM201ATSC A/85, short form
LEVELAPI2_STANDARD_ARIB_TRB32300ARIB TR-B32
LEVELAPI2_STANDARD_FREETV_OP59400Free TV Australia OP-59
LEVELAPI2_STANDARD_ITU_BS1770_1500ITU-R BS.1770-1, ungated
LEVELAPI2_STANDARD_ITU_BS1770_4501ITU-R BS.1770-4, gated

MeterTypes

Used as the meterType argument in GetLevel() and related functions. Values marked (*) are generally for testing only and not intended for display to end users.

ValueDescription
LUIntegrated loudness, calibrated LU (gated)
LUFSIntegrated loudness, uncalibrated LUFS (gated)
LU_UNGATEDCalibrated LU without gating (*)
LUFS_UNGATEDUncalibrated LUFS without gating (*)
LU_MinMinimum calibrated LU level (*)
LU_DialogCalibrated dialog LU level (*)
LUFS_DialogUncalibrated dialog LUFS level (*)
LU_Dialog_UNGATEDDialog LU without gating (*)
Dialog_percentagePercentage of dialog detected in the audio
LU_Max_MMaximum momentary level (calibrated LU)
LUFS_Max_MMaximum momentary level (uncalibrated LUFS)
LU_Max_SMaximum short-term level, 3 s window (calibrated LU)
LUFS_Max_SMaximum short-term level, 3 s window (uncalibrated LUFS)
LRALoudness range (LRA_High − LRA_Low)
LRA_Range_LowLow boundary of the LRA calculation (*)
LRA_Range_HighHigh boundary of the LRA calculation (*)
LU_BS1771Realtime calibrated LU (BS.1771) (*)
LU_BS1771_FilteredRealtime calibrated LU with meter filter
LU_BS1771_Shortterm3 s realtime calibrated LU
LUFS_BS1771_Shortterm3 s realtime uncalibrated LUFS (*)
PPMRealtime PPM level in dB, calibrated (must be enabled)
PPM_MaxMaximum PPM value in dB, calibrated
RMS_MaxMaximum RMS value in dBFS (must be enabled)
PeakMaximum sample peak (*)
TruePeakMaximum true inter-sample peak
PSRPeak-to-Short-term Ratio
PeakLoudnessPLR, Peak Loudness Range
Limited_dBAmount of limiting applied in dB (*)
AdjustLevelThe calculated normalization gain in dB
AdjustLevel_NoPeakNormalization gain ignoring peak limiting (*)

06 Dynlib Initialization

These functions are called immediately after loading the dynamic library, before creating any handles.

bool SetCertificate(const char *certificate_string, size_t bufferSize)

Must be the first call made to the loaded library. Validates your license certificate. Returns true on success.

  • arg 1Pointer to a char array containing the certificate text
  • arg 2Total byte length of the certificate text (max 10 × 1024)
bool GetErrorString(const char *error_string, size_t bufferSize)

Call only when SetCertificate() has returned false. Fills the provided buffer with the error description.

  • arg 1Pointer to an empty char array to receive the error text
  • arg 2Size of the buffer (max 10 × 1024)
LevelAPI2Ptr Create_LevelAPI2(const int channels, const unsigned int samplerate, const int bitdepth)

Creates and initializes a LevelAPI2 instance. Returns a valid handle, or NULL on failure. Every subsequent function call requires this handle as its first argument. Do not modify the returned value.

  • channelsNumber of audio channels, e.g. 2 for stereo
  • samplerateSample rate of the source material, e.g. 48000
  • bitdepthBit depth used for dithering during gain adjustment: 16, 24, or 32
void Destroy_LevelAPI2(LevelAPI2Ptr handle)

Releases all resources associated with the given handle. Must be called for every handle created with Create_LevelAPI2().

int GetRevision()

Returns the integer revision number of the loaded library binary. Useful for version checks at startup.

07 Library Initialization

bool SetPresetEBU_R128(LevelAPI2Ptr handle)

Convenience preset for full EBU R128 compliance. Equivalent to calling: SetCalibration(LU, −23), SetAdjustTargetType(LU), SetAdjustTargetLevel(0), SetRelativeGate(−10), SetDialogGate(false). Identical to SetPresetStandard(LEVELAPI2_STANDARD_EBU_R128), which is the newer form.

Returns true on success.
bool SetPresetATSC(LevelAPI2Ptr handle)

Convenience preset for ATSC A/85 (CALM Act) long-form content. Equivalent to: SetCalibration(LU, −24), SetAdjustTargetType(LU_Dialog), SetAdjustTargetLevel(0), SetRelativeGate(0), SetDialogGate(true). The dialogue gate A/85 requires is part of the preset: no second call is needed. Relative gating is off, which is what A/85 asks for on long-form content, and normalization moves the dialogue loudness onto the target rather than the full mix. Identical to SetPresetStandard(LEVELAPI2_STANDARD_ATSC_A85_LONGFORM).

Returns true on success.

You can combine presets with manual overrides. For example: call SetPresetEBU_R128() then SetCalibration() to override only the calibration value.

The following functions return stream format properties set during initialization:

unsigned int GetChannels(LevelAPI2Ptr)

Returns the number of channels.

unsigned int GetSamplerate(LevelAPI2Ptr)

Returns the sample rate.

08 Processing

bool AnalyseInterleaved(LevelAPI2Ptr handle, float* samples, const unsigned int numframes)

Analyses a block of interleaved float samples. Call repeatedly to process a complete audio stream. For best performance, pass blocks of at least 256 frames.

  • samplesPointer to an interleaved float buffer
  • numframesNumber of sample frames (= total samples ÷ channels). Maximum: 16384
Returns true on success.
bool Analyse(LevelAPI2Ptr handle, float* samples, const unsigned int numframes)

Non-interleaved variant. Use when your audio data is in separate channel buffers rather than interleaved.

bool AdjustInterleaved(LevelAPI2Ptr handle, float* samples, const unsigned int numframes)

Applies loudness normalization gain to the sample buffer in-place. Uses the AdjustLevel calculated by AnalyseInterleaved() (same handle), or the value set manually with SetAdjustLevel(). The peak limiter activates automatically when required.

  • samplesPointer to the float buffer to be modified in-place
  • numframesNumber of sample frames
bool Adjust(LevelAPI2Ptr handle, float* samples, const unsigned int numframes)

Non-interleaved variant. Use when your audio data is in separate channel buffers rather than interleaved.

bool AnalyseFile(LevelAPI2Ptr handle, const char* filepath)

Analyses an audio file directly. Supported formats: WAV, AIFF, FLAC.

bool AdjustFile(LevelAPI2Ptr handle, const char* infile, const char* outfile)

Normalizes an audio file and writes the result to a new file. The output path must not already exist.

  • infilePath to the source audio file (WAV / AIFF / FLAC)
  • outfilePath for the normalized output file (must not exist)

09 Results

float GetLevel(LevelAPI2Ptr handle, enum MeterTypes meterType, const int channel)

Returns a meter value in dB for the given meter type and channel.

  • meterTypeA value from the MeterTypes enum, e.g. LU, LRA, TruePeak
  • channelChannel index (0-based), or -1 for the combined result across all channels
float GetAdjustLevel(LevelAPI2Ptr handle, const bool ignore_peak_limit)

Returns the calculated normalization gain in dB. Use this to apply the gain manually, or save it for later use with a separate handle. Pass false (default) to include peak limiting in the calculation.

bool SetAdjustLevel(LevelAPI2Ptr handle, const float adjustLevel)

Manually sets the normalization gain in dB. Not needed when using the same handle for both analysis and adjustment. Use this when separating the analyse and adjust passes, for example to analyse multiple files first, then adjust them in a second pass.

unsigned int GetAdjustLatency(LevelAPI2Ptr handle)

Returns the latency in samples introduced by the peak limiter during adjustment (approximately 20 ms). Account for this when synchronizing audio streams.

10 Advanced Settings

These functions modify measurement behaviour beyond the standard presets. For straightforward EBU R128 or ATSC A/85 compliance, the preset functions are sufficient. All Set functions return true on success.

Adjust Target

int GetAdjustTargetType(LevelAPI2Ptr)

Returns the current target meter type used for normalization.

bool SetAdjustTargetType(LevelAPI2Ptr, enum MeterTypes)

Sets the meter that Adjust() normalizes onto the target. Accepted values:

  • full mixLU (default), LUFS
  • dialogueLU_Dialog, LUFS_Dialog, LU_Dialog_UNGATED, LUFS_Dialog_UNGATED. Anchor-based practices normalize on dialogue loudness rather than on the programme, which is what SetPresetATSC() and the two anchor-based standard presets select
  • levelPPM, PPM_Max, Peak, TruePeak
Returns false for any other meter type, leaving the setting unchanged.
float GetAdjustTargetLevel(LevelAPI2Ptr)

Returns the target loudness level in dB.

bool SetAdjustTargetLevel(LevelAPI2Ptr, const float targetLevel)

Sets the normalization target level in dB. Default is 0 (0 LU = −23 LUFS for EBU, −24 LKFS for ATSC).

Channels

enum SurroundOrders GetSurroundOrder(LevelAPI2Ptr)

Returns the current surround channel order.

bool SetSurroundOrder(LevelAPI2Ptr, enum SurroundOrders)

Sets the channel order for surround sources. Also automatically configures dialog channel detection. Use SMTPE_ITU_AC3, FilmDolbyDigital, or DTS_ProControl.

bool SetChannelWeight(LevelAPI2Ptr, const int channel, const float weight)

Overrides the per-channel weighting used for loudness measurement. Only relevant for surround configurations. SetSurroundOrder() handles this automatically for standard layouts.

  • channelChannel index, 0 to max_channels
  • weightWeight in dB

Gates

float GetRelativeGate(LevelAPI2Ptr)

Returns the relative gate threshold in dB.

bool SetRelativeGate(LevelAPI2Ptr, const float threshold)

Sets the relative gate threshold. Default: −10 dB (EBU R128), 0 (ATSC, no gating).

bool GetDialogGate(LevelAPI2Ptr)

Returns whether the dialog gate is enabled.

bool SetDialogGate(LevelAPI2Ptr, const bool on)

Enables or disables the dialog gate. Required for ATSC CALM Act compliance on long-form content, and already set by SetPresetATSC() and by the two anchor-based standard presets. Call it directly only to depart from a preset.

Calibration

float GetCalibration(LevelAPI2Ptr, enum MeterTypes)

Returns the calibration offset in dB for a given meter type.

bool SetCalibration(LevelAPI2Ptr, enum MeterTypes, const float calibration)

Sets calibration offset. Defaults: −23 dB for EBU LU, −24 dB for ATSC. Meter type: LU, PPM, or TruePeak.

Limiter

bool GetLimiterEnable(LevelAPI2Ptr)

Returns whether the limiter is active.

bool SetLimiterEnable(LevelAPI2Ptr, const bool on)

Forces the limiter on or off. By default it activates automatically when needed.

float GetLimiterThreshold(LevelAPI2Ptr)

Returns the limiter analysis threshold in dBFS.

bool SetLimiterThreshold(LevelAPI2Ptr, const float dB)

Sets the limiter threshold used during analysis. Default: −1.0 dBFS (prevents TruePeak clipping).

float GetLimiterThresholdAdjust(LevelAPI2Ptr)

Returns the limiter threshold used during the adjustment pass.

bool SetLimiterThresholdAdjust(LevelAPI2Ptr, const float dB)

Sets the limiter threshold for the adjustment pass. May differ from the analysis threshold because TruePeak and sample peak are not identical.

Time Window

int GetTimeWindow(LevelAPI2Ptr)

Returns the current measurement window in seconds.

bool SetTimeWindow(LevelAPI2Ptr, const unsigned int seconds)

Limits the measurement to a rolling time window. Useful for continuous realtime metering. Each additional hour of window increases memory use by ~1.44 MB.

Legacy Meters (PPM / RMS)

PPM and RMS are legacy meters, disabled by default. Enable them only if your integration specifically requires them.

bool SetPPMEnable(LevelAPI2Ptr, const bool)

Enables or disables PPM measurement. Default: off.

bool SetRMSEnable(LevelAPI2Ptr, const bool)

Enables or disables RMS measurement. Default: off.

Histogram Access

Thread safety: Histogram functions are not thread-safe. Complete your read or copy before calling any other function on the same handle.

Raw histogram data is available for detailed loudness distribution analysis:

FunctionReturns
GetHistogramLUFS(handle, &size)Integrated loudness histogram
GetHistogramLUFS_Dialog(handle, &size)Dialog loudness histogram
GetHistogramLUFS_S(handle, &size)Short-term loudness and LRA histogram
GetHistogramPSR(handle, &size)Peak-to-Short-term Ratio histogram

Conversion helpers between histogram positions and dB values:

int LUFS2tablePos(LevelAPI2Ptr, float lufs)

Converts a LUFS value to a histogram array index.

float tablePos2LUFS(LevelAPI2Ptr, int pos)

Converts a histogram array index to a LUFS value.

float dB2float(LevelAPI2Ptr, float dB)

Converts a dB value to a linear float.

float float2dB(LevelAPI2Ptr, float flt)

Converts a linear float to dB.

11 Loudness Standards

A technician handed a delivery spec does not want to configure a meter. They want to pick the name printed on the document their broadcaster gave them. SetPresetStandard() takes one value from LoudnessStandards and applies that practice in full: the target, the relative gate, the dialogue gate, the true-peak ceiling, and which meter normalization moves onto the target.

Ten standards ship in this build. The library can also list and describe them at runtime, so a preset dropdown is generated from the loaded library rather than typed out by hand. A library update then adds standards to your product without a line of code changing.

For the plain-language version, with what each practice asks for and why the variants differ, see levelapi.dev/loudness-standards.

LKFS and LUFS are two names for the same unit. LKFS is the ITU and ATSC spelling, used in ITU-R BS.1770, ATSC A/85, ARIB TR-B32 and Free TV OP-59; LUFS is the EBU spelling, used in EBU R 128. The measurement is identical and the numbers are identical, and 1 LU equals 1 dB either way. Each row below is spelled the way its own document spells it.

What each preset applies

A relative gate of 0 means ungated. The normalizes column is the meter Adjust() moves onto the target: an anchor-based practice normalizes dialogue loudness rather than the full mix. The limiter threshold is set but the limiter is never switched on or off by a preset, so a preset cannot move your audio for a reason you did not ask for.

StandardConstantTargetRelative gateDialogue gateCeilingNormalizes
EBU R 128EBU_R128−23 LUFS−10 LUoff−1 dBTPLU
EBU R 128 (2010 edition)EBU_R128_2010−23 LUFS−8 LUoff−1 dBTPLU
EBU R 128 s2, streamingEBU_R128_S2_STREAMING−18 LUFS−10 LUoff−1 dBTPLU
EBU R 128 s4, cinematicEBU_R128_S4_CINEMA−23 LUFS−10 LUon−1 dBTPLU_Dialog
ATSC A/85, long formATSC_A85_LONGFORM−24 LKFS0, ungatedon−2 dBTPLU_Dialog
ATSC A/85, short formATSC_A85_SHORTFORM−24 LKFS−10 LUoff−2 dBTPLU
ARIB TR-B32ARIB_TRB32−24 LKFS−10 LUoff−1 dBTPLU
Free TV Australia OP-59FREETV_OP59−24 LKFS−10 LUoff−2 dBTPLU
ITU-R BS.1770-1 (ungated)ITU_BS1770_1−23 LUFS0, ungatedoff−1 dBTPLU
ITU-R BS.1770-4 (gated)ITU_BS1770_4−23 LUFS−10 LUoff−1 dBTPLU

Constants are shown without their LEVELAPI2_STANDARD_ prefix. The full names and their numeric values are in section 05.

ATSC A/85's two variants use opposite settings, and the difference is content length. Long-form programme material is measured dialogue-gated with relative gating off. Short-form material, meaning commercials and promos, is measured on the full mix over its whole duration with relative gating on. Picking the wrong one of the two is the most common way to be out of compliance while believing you are in it.

Two rows are anchor-based and behave differently from the rest: EBU_R128_S4_CINEMA and ATSC_A85_LONGFORM normalize dialogue loudness rather than programme loudness. The dialogue meters fall back to the full mix on their own when the material carries less than 10% dialogue.

ITU-R BS.1770 publishes no target level. It defines a measurement, not a delivery practice, so the two ITU rows carry the library's own −23 default in order to be usable on their own. BS.1770 spells the unit LKFS; those two rows are labelled LUFS here because that is what the library reports for them at runtime through GetStandardDescription().

Function reference

bool SetPresetStandard(LevelAPI2Ptr handle, enum LoudnessStandards)

Applies one standard in full. Sets the calibration, the adjust target type and level, the relative gate, the dialogue gate and the limiter threshold. It touches nothing else: channel weights, surround order and the time window are left exactly as you set them.

Returns false if this build does not know the value, and in that case changes nothing. Always check it.
enum LoudnessStandards GetPresetStandard(LevelAPI2Ptr handle)

Returns the standard last applied, or LEVELAPI2_STANDARD_NONE if none has been. Note that this is the last value set: changing a constituent setting by hand afterwards, with SetRelativeGate() for instance, does not clear it. Treat it as a record of what was selected, not as proof of the current configuration. To report what is actually in force, read the individual getters.

int GetStandardCount(LevelAPI2Ptr handle)

Returns how many standards this build knows.

enum LoudnessStandards GetStandardAt(LevelAPI2Ptr handle, const int index)

Returns the standard at a position, for walking the list. Use with GetStandardCount() to build a menu that cannot disagree with the loaded library.

  • index0 to GetStandardCount() − 1. Out of range returns LEVELAPI2_STANDARD_NONE
const char* GetStandardName(LevelAPI2Ptr handle, enum LoudnessStandards)

The display name, for example "ATSC A/85 - long form". Returns an empty string for a value this build does not know. The pointer is owned by the library and stays valid for its lifetime.

const char* GetStandardDescription(LevelAPI2Ptr handle, enum LoudnessStandards)

A one-line description of what the preset actually applies, for example "−24 LKFS, dialogue gate on, NO relative gate, −2 dBTP. US broadcast, programmes." Worth surfacing in your interface next to the name: A/85 itself warns that preset labels differ between products and that users should verify the algorithm rather than trust the label.

Building a preset menu from the library

Enumerate rather than hardcode. A host built against a newer header but running against an older library then sees a shorter list, never a wrong answer.

C
// Populate a dropdown with whatever this build supports
int count = api.GetStandardCount(h);
for (int i = 0; i < count; i++)
{
    enum LoudnessStandards s = api.GetStandardAt(h, i);
    add_menu_item(api.GetStandardName(h, s),
                  api.GetStandardDescription(h, s),
                  (int)s);
}

// Apply the user's choice, and check the result
if (!api.SetPresetStandard(h, chosen))
{
    // This library build does not know that standard.
    // Nothing was changed. Do not measure and report anyway.
    report_unsupported_standard(chosen);
}

Check the return value of SetPresetStandard(). An older library handed a newer standard's value refuses it and changes nothing, which is only useful if the caller notices. Unchecked, a version mismatch becomes a measurement made to the wrong practice at the customer's end.

The two original presets

SetPresetEBU_R128() and SetPresetATSC() are unchanged and are not deprecated. They are exactly SetPresetStandard(LEVELAPI2_STANDARD_EBU_R128) and SetPresetStandard(LEVELAPI2_STANDARD_ATSC_A85_LONGFORM). Existing integrations need no edit.

How far these are verified

All ten presets are verified to configure exactly what their own standard's document specifies. Three are additionally verified against that body's own published reference audio: EBU R 128, ITU-R BS.1770 and ARIB TR-B32. The other seven have no published reference set to measure against: only some standards bodies release conformance audio. Full per-file results, with the tolerance and the document clause beside every measurement, are at levelapi.dev/compliance.

These are measurements made in-house against reference files the standards bodies publish. Nothing here has been assessed or certified by the EBU, the ITU, ATSC, ARIB or Free TV Australia.

12 Technical Specifications

EBU R128

MeasurementITU BS.1770-2
Relative gate−10 LU
0 LU equals−23 LUFS
Target tolerance0 LU ± 6 LU

ATSC A/85

MeasurementITU BS.1770-1
Relative gateNone
0 LU equals−24 LKFS
Target tolerance0 LU ± 6 LU

Surround channel orders

ModeChannel order
SMPTE / ITUL, R, C, Lfe, Ls, Rs
Film / DolbyL, C, R, Ls, Rs, Lfe
DTSL, R, Ls, Rs, C, Lfe

13 Realtime Adjuster

The realtime adjuster holds a live signal on a loudness target while the audio is passing through it. It measures and corrects in a single pass, with no analysis stage and no knowledge of what is coming next, which is what allows it to sit in a path where the audio has to come out now: a live console insert, a monitor path, a stream encoder, a broadcast chain.

It modulates level toward the target rather than reshaping the signal, so the dynamics of the material are left alone.

EBU R128 asks produced programme to sit within ±0.5 LU of target, and permits ±1.0 LU on less predictable material such as live mixed programme. That wider window is the one the adjuster works to, once it is warmed up. Treat it as a target rather than a guarantee: difficult live material with extreme dynamics can land outside the band.

The adjuster is a separate object from the measurement API, with its own handle type (LevelAPI2_RealtimeAdjusterPtr) and its own create and destroy calls. A single process can run both, and any number of either.

Realtime adjuster

Target band±1.0 LU (R128 live)
Latency, limiter on20 ms
Latency, limiter off0 ms
CorrectionGain only

Stream limits

Maximum channels16
Frames per call16384
Sample format32-bit float
BuffersAdjusted in place

Dialogue gating is a library capability, not an adjuster one. LevelAPI2 supports ATSC A/85 with dialogue intelligence. The realtime adjuster does not currently perform dialogue gating, so for A/85 workflows that depend on it, use the measurement API. The library is under active development, and extensions or custom integrations can be arranged under a license. If your product needs something that is not covered here, get in touch.

Integration overview

  1. Load the library and validate your certificate

    Identical to the measurement API. SetCertificate() is a global call made once per process after loading, not once per handle.

  2. Create an adjuster

    Create_RealtimeAdjuster(channels, samplerate, bitdepth) returns the handle every other call takes as its first argument. There is no standard preset pair to choose here: the adjuster works to a LUFS target you set directly.

  3. Set the preset and the target

    The constructor takes neither, so both are applied immediately afterwards with RealtimeAdjuster_SetPreset() and RealtimeAdjuster_SetTargetLUFS().

  4. Call Adjust() on every block

    The buffer is modified in place. Nothing else has to happen per block, and no result has to be read for the adjuster to do its job.

C
#include "LevelAPI2_dynlib.h"

// 1. Load library and validate certificate (once per process)
struct LevelAPI2_FunctionHandles api;
void* lib = LevelAPI2_LoadDLL(library_path, &api);
api.SetCertificate(cert_string, strlen(cert_string));

// 2. Create an adjuster instance
LevelAPI2_RealtimeAdjusterPtr h =
    api.Create_RealtimeAdjuster(channels, samplerate, bitdepth);

// 3. Configure: preset and target loudness
api.RealtimeAdjuster_SetPreset(h, 1);          // 1 = medium (default)
api.RealtimeAdjuster_SetTargetLUFS(h, -23.0f); // EBU R128 programme target

// 4. Per audio block: adjust in place (planar buffers)
api.RealtimeAdjuster_Adjust(h, channel_pointers, numframes);

// Optional: read what it is doing, for display
float applied = api.RealtimeAdjuster_GetLastAppliedGainDb(h);
float lufs    = api.RealtimeAdjuster_GetLUFS(h);

// Cleanup
api.Destroy_RealtimeAdjuster(h);
LevelAPI2_UnloadDLL(lib);

That is the whole integration. Everything below is optional refinement.

Latency and the limiter

The adjuster's only delay is its built-in peak limiter: 20 ms. Level modulation itself needs no look-ahead, so with the limiter disabled the adjuster runs at zero latency.

RealtimeAdjuster_SetLimiterEnabled(handle, false) switches it off. Without the limiter a fast transient can clip, so peak protection has to exist somewhere in your chain.

Most broadcast and production chains already have a limiter. If yours does, disabling the built-in one removes the delay rather than stacking two limiters and paying 20 ms for the second one.

Warmup and settling

On the default preset the adjuster corrects from the first block of audio. There is no warmup period to wait through, and no opening stretch during which the output plays closer to the input level than to the target.

Warmup is a property of the preset rather than of the adjuster, so a preset can choose to ease its correction in instead. Of the three, only Slow does: it brings gain correction up over its first 30 seconds. Medium and Fast have none. Because the value comes from the preset, calling SetPreset() mid-stream changes it.

Separately, the adjuster's own integrated meter needs audio behind it before the loudness it reports means anything, so calibration strength ramps in over 5 seconds on every preset. That clock starts where the main warmup ends, so under a preset that has one the total wait is the two added together; with no warmup it starts at the first block. The moment it reaches zero is also the moment GetLUFS() is fully settled, which makes it the honest gate for anything that displays or logs a measured level.

Four read-outs report the two clocks, all in seconds:

  • GetWarmupSecondsLeft() and GetCalibrationSecondsLeft() count down to 0 and stay there. <= 0 is the test for done.
  • GetWarmupSecondsMax() and GetCalibrationSecondsMax() report the lengths the current preset is configured for, so a countdown can be drawn without hardcoding a duration. A maximum of 0 means the preset has no such period at all.

A completed warmup is latched. Switching preset while audio is running can shorten a warmup still in progress, but it never re-opens one that has already finished.

Presets

ValuePresetNotes
0SlowProvisional. Tuning may change between versions.
1MediumThe tuned default and the recommended choice.
2FastProvisional. Tuning may change between versions.

SetPreset() takes a plain unsigned int, so the legal values are listed here rather than expressed as an enum across the library boundary. The presets differ in how quickly the adjuster responds and how strongly it weights recent audio against the programme so far. They are close to each other by design: the intent is a stable output level, and the reason to change preset is that the output is not reaching the target, not that you want a different sound.

They also differ in warmup. Slow brings its correction up over the first 30 seconds; medium and fast correct from the first block. See Warmup and settling.

Medium is what LevelAnchor ships with, hard-coded, and it is what has been tuned against real material.

Threading and lifecycle

Create and Reset are heavy calls. Never call either from your audio thread. They allocate and initialize the adjuster's internal meters.

One adjuster instance handles one stream. Create as many as you have streams. Reset() reconfigures an existing instance for a new stream format and clears its measurement history.

Buffers are modified in place. Adjust() takes planar channel pointers, AdjustInterleaved() takes one interleaved buffer. Maximum 16 channels, and at most 16384 sample frames per call, so split larger blocks across successive calls.

Setting the target with SetTargetLUFS() while audio is running is supported and is the normal way to change target. The adjuster ramps toward the new value rather than jumping to it.

Function reference

All functions below take a LevelAPI2_RealtimeAdjusterPtr as their first argument. All Set functions return true on success.

LevelAPI2_RealtimeAdjusterPtr Create_RealtimeAdjuster(const unsigned int channels, const unsigned int samplerate, const unsigned int bitdepth)

Creates an adjuster instance. Returns a valid handle, or NULL on failure. Every subsequent function call requires this handle as its first argument.

  • channelsNumber of audio channels, e.g. 2 for stereo. Maximum 16
  • samplerateSample rate of the stream, e.g. 48000
  • bitdepthThe word length the host will quantise this processor's output to, which decides whether dither is applied on the way out. It describes what happens downstream, not the depth of the incoming material. Pass 0 for a float host: 0, and any value below 8, means no dither at all. Otherwise pass the output depth, e.g. 16 or 24

Not the same argument as Create_LevelAPI2(). The two calls take a parameter of the same name and it means different things: on the two-pass API it is the bit depth of the source material, and here it is the output word length. A float signal chain wants 0 here.

void Destroy_RealtimeAdjuster(LevelAPI2_RealtimeAdjusterPtr handle)

Destroys the instance and frees its resources.

bool RealtimeAdjuster_Adjust(LevelAPI2_RealtimeAdjusterPtr handle, float** samples, const unsigned int numframes)

Applies the adjuster to a block of planar float buffers, in place. Call once per audio block.

  • samplesArray of channel pointers, one float buffer per channel
  • numframesNumber of sample frames per channel. Maximum: 16384
Returns true on success.
bool RealtimeAdjuster_AdjustInterleaved(LevelAPI2_RealtimeAdjusterPtr handle, float* samples, const unsigned int numframes)

Interleaved variant. Use when your audio is in a single interleaved buffer rather than separate channel buffers.

bool RealtimeAdjuster_Reset(LevelAPI2_RealtimeAdjusterPtr handle, const unsigned int channels, const unsigned int samplerate, const unsigned int bitdepth)

Reconfigures the instance for a new stream format and clears its measurement history. A heavy call: never run it on the audio thread.

  • channelsNumber of audio channels in the new stream. Maximum 16
  • samplerateSample rate of the new stream
  • bitdepthOutput word length, exactly as in Create_RealtimeAdjuster() above: 0 for a float host and therefore no dither, otherwise the depth the host quantises to
Returns true on success.
int RealtimeAdjuster_GetPreset(LevelAPI2_RealtimeAdjusterPtr)

Returns the current preset as 0, 1 or 2.

bool RealtimeAdjuster_SetPreset(LevelAPI2_RealtimeAdjusterPtr, const unsigned int preset)

Sets the response preset. See the preset table above for legal values. Default: 1 (medium).

float RealtimeAdjuster_GetTargetLUFS(LevelAPI2_RealtimeAdjusterPtr)

Returns the current target loudness in LUFS.

bool RealtimeAdjuster_SetTargetLUFS(LevelAPI2_RealtimeAdjusterPtr, const float lufs)

Sets the target loudness in LUFS, for example -23.0 for EBU R128 or -24.0 for ATSC A/85. Safe to call while audio is running.

float RealtimeAdjuster_GetLastAppliedGainDb(LevelAPI2_RealtimeAdjusterPtr)

Returns the gain in dB currently being applied. This is the value to show a user who wants to see the adjuster working.

float RealtimeAdjuster_GetWarmupSecondsLeft(LevelAPI2_RealtimeAdjusterPtr)

Seconds of warmup left before gain correction is at full strength. Counts down to 0 and stays there. Returns 0 throughout on a preset with no warmup, which includes the default.

float RealtimeAdjuster_GetWarmupSecondsMax(LevelAPI2_RealtimeAdjusterPtr)

The warmup length the current preset is configured for, in seconds, so a progress display needs no hardcoded duration. 0 means the preset has no warmup.

float RealtimeAdjuster_GetCalibrationSecondsLeft(LevelAPI2_RealtimeAdjusterPtr)

Seconds left before the integrated meter is settled. Holds at its maximum until the main warmup completes, then counts down to 0. GetLUFS() is fully settled when this reaches 0.

float RealtimeAdjuster_GetCalibrationSecondsMax(LevelAPI2_RealtimeAdjusterPtr)

The calibration ramp the current preset is configured for, in seconds. 5 on every preset at present.

bool RealtimeAdjuster_GetLimiterEnabled(LevelAPI2_RealtimeAdjusterPtr)

Returns whether the built-in peak limiter is active.

bool RealtimeAdjuster_SetLimiterEnabled(LevelAPI2_RealtimeAdjusterPtr, const bool enable)

Enables or disables the limiter. Enabled by default, costing 20 ms of latency. Disabled, the adjuster runs at zero latency and peak protection becomes your responsibility.

Informational read-outs

These report what the adjuster is measuring. None of them is required to run it, and an integration that only needs correct loudness can ignore this section entirely.

float RealtimeAdjuster_GetLUFS(LevelAPI2_RealtimeAdjusterPtr)

Integrated loudness of the adjusted output, in LUFS. It comes from a meter of its own, started once any preset warmup has completed, and is fully settled when GetCalibrationSecondsLeft() reaches 0.

float RealtimeAdjuster_GetLevel(LevelAPI2_RealtimeAdjusterPtr handle, enum MeterTypes meterType, const int channel)

Any value from the MeterTypes enum, read from the adjuster's own output meter. Same semantics as GetLevel() on the measurement API, including -1 for the combined result across all channels.

float RealtimeAdjuster_GetBS1771(LevelAPI2_RealtimeAdjusterPtr)

Realtime BS.1771 loudness of the input, before adjustment. Useful for showing what arrived against what left.

float RealtimeAdjuster_GetPSR(LevelAPI2_RealtimeAdjusterPtr)

Peak-to-Short-term Ratio of the output, over the recent short window.

float RealtimeAdjuster_GetLRA(LevelAPI2_RealtimeAdjusterPtr)

Loudness range of the output, over the recent short window.

LevelAnchor, the free OBS plugin built on this API, deliberately displays none of these. It shows a target and an output level, and nothing else. They are here because a product might want them, not because the adjuster needs them.

Worked example

LevelAnchor, the free OBS loudness plugin, is a complete, shipping integration of this exact header: a Windows VST plugin that holds a live audio source on a LUFS target. Its source is public, and the released binary is built from it. The file to read is Source/LevelApiDynamic.cpp, which loads the library, resolves the function table, creates one adjuster per plugin instance and calls Adjust() from the audio callback.


14 LevelShow2: Overview

LevelShow2 is a Windows DirectShow filter that wraps LevelAPI2 for use in Windows media pipeline applications. It is included with every LevelAPI2 license.

It exposes the same measurement and normalization capabilities as LevelAPI2 through two interfaces: a graphical properties page for manual operation, and the ILevelShow2 COM interface for programmatic control.

LevelShow2 is primarily relevant for Windows-based broadcast automation pipelines. For cross-platform or non-DirectShow use, LevelAPI2 directly is the correct integration path.

15 LevelShow2: Usage

LevelShow2 can be implemented in two ways:

  • Single instance: one filter handles both analysis and adjustment in sequence. Simpler workflow, suitable for most use cases.
  • Two instances: one filter analyses, a second applies the adjustment. Allows analysing multiple files first, then normalizing them in a separate pass.

Basic workflow

  1. 1

    Build your DirectShow graph

    Connect your audio source to the LevelShow2 input pin and connect the output pin downstream.

  2. 2

    Configure analysis settings

    Enter your certificate, set Mode to Analyse, select your standard (EBU R128 or ATSC), and if using surround, select the correct channel order.

  3. 3

    Stream the audio

    Run the graph to pass all audio through the filter. LevelShow2 accumulates measurement data throughout the stream.

  4. 4

    Switch to Adjust mode

    Set Mode to Adjust. The filter automatically configures itself with the calculated normalization gain. To reset for a new stream, set Mode to −1.

  5. 5

    Re-stream to apply normalization

    Pass the audio through the filter again. The output is now level-normalized. Alternatively, pass the AdjustLevel value to a second instance set to Adjust mode.

16 LevelShow2: Programmatic Interface

Query the ILevelShow2 interface via IUnknown::QueryInterface to control the filter programmatically. The interface mirrors the graphical controls exactly.

License

MethodDescription
put_Certificate(char*)Supply your license certificate string
get_CertificateError(char*)Retrieve error text if certificate is invalid
get_License(bool*)Returns true if the current certificate is valid

Mode

ValueMeaning
0Analyse mode
1Adjust mode
−1Reset, clears measurement history and returns to Analyse

Analyse settings

MethodValues / DefaultDescription
put/get_MeasureType0=EBU R128, 1=ATSC, 2=CustomConvenience preset, sets all related parameters
put/get_LURelativeGatebool (true=on)Enable/disable the −10 LU relative gate
put/get_DialogGatebool (true=on)Enable/disable dialog gate (ATSC CALM)
put/get_SurroundOrder0=SMPTE, 1=Film, 2=DTSSurround channel order
put/get_CalibrationLUfloat, default −23.00LU calibration offset in dB
put/get_CalibrationPPMfloat, default −9.00PPM calibration offset in dB
put/get_MeterType0=LU, 1=PPM, 2=PeakTarget meter type for normalization
put/get_TargetLevelfloat, default 0.00Target level in dB

Results

MethodDescription
get_LULevel(float*)Integrated loudness in LU
get_PPMLevel(float*)PPM level
get_PeakLevel(float*)True peak level
get_MaxMLevel(float*)Maximum momentary level
get_MaxSLevel(float*)Maximum short-term level
get_LRALevel(float*)Loudness range
get_AdjustLevel(float*)Calculated normalization gain in dB, use with put_AdjustLevel on a second instance
put_AdjustLevel(float)Manually sets the adjustment gain, bypasses analysis
get_MLevel(float*)Current momentary level (call per processed block for realtime metering)
get_SLevel(float*)Current short-term level (call per processed block)
get_Warning(char*)Returns a warning string if the adjust level is abnormal, NULL if none

17 References

  • tech.ebu.ch/loudness: EBU R128 recommendation, official documents, guidelines, and introduction videos
  • ATSC A/85 Recommendation, available from the ATSC website
  • ITU-R BS.1770, the foundational loudness metering standard

A complete trial certificate and the header file (LevelAPI2_dynlib.h) are available on request. Licensing and contact →