Usage

Warning

None of the example code below has been tested yet.

Use at your own risk!

Setup

The main interface with this library is the Meter class.

>>> import threading
>>> import time
>>> import numpy as np
>>> from lupy import Meter

Typical audio interface libraries have a configurable block size which must be known before creating the Meter. For this example we will be using python-sounddevice.

>>> import sounddevice as sd
>>> block_size = 512
>>> num_channels = 1
>>> meter = Meter(block_size=block_size, num_channels=1)

Configuring Measurements

By default all measurement types are enabled. You can selectively disable measurements you don’t need to reduce processing overhead:

All *_enabled parameters default to True, so they are not required when all measurements are desired:

>>> meter = Meter(
...     block_size=block_size,
...     num_channels=1,
...     momentary_enabled=True,
...     short_term_enabled=True,
...     lra_enabled=True,
...     true_peak_enabled=True,
... )

For example, if you only need Integrated Loudness you can disable the others:

>>> meter = Meter(
...     block_size=block_size,
...     num_channels=1,
...     momentary_enabled=False,
...     short_term_enabled=False,
...     lra_enabled=False,
...     true_peak_enabled=False,
... )

Important

Loudness Range (LRA) calculation depends on Short-Term Loudness. If short_term_enabled is False, lra_enabled must also be False. Passing short_term_enabled=False, lra_enabled=True raises a ValueError.

Now we need to define a callback for the sample data. The samples in the callback’s indata are shaped as (num_samples, num_channels) so we need to swap the axes.

Then we make sure there is enough room in the meter’s sample buffer using Meter.can_write() and give it to the meter using its Meter.write() method.

The process=False argument is set to avoid doing any more work within the callback (since it’s called from the audio thread). Instead we’ll use a threading.Event to signal when it’s been written.

Handling Input

>>> samples_ready = threading.Event()
>>> def audio_callback(indata, outdata, frames, time, status):
...     if status:
...         print(status)
...     indata = np.swapaxes(indata, 0, 1)
...     if not meter.can_write():
...         print('buffer full!')
...     meter.write(indata, process=False)
...     samples_ready.set()

Processing

The processing can then be done from the main loop using the Meter.can_process() and Meter.process() methods. We’ll limit the total duration to ten seconds for this example.

>>> max_duration = 10
>>> time_remaining = time.time() + max_duration
>>> with sd.InputStream(
...     device=None,
...     channels=meter.num_channels,
...     samplerate=meter.sample_rate,
...     callback=audio_callback,
... ):
...     while True:
...         samples_ready.wait()
...         samples_ready.clear()
...         if meter.can_process():
...             meter.process()
...         if time.time() >= time_remaining:
...             break

Channel Layout

When measuring more than one channel, the expected layout should follow the order below. This is important because channels are weighted differently in the calculations.

Stereo

Index

Name

0

Left

1

Right

LCR

Index

Name

0

Left

1

Center

2

Right

Surround

Index

Name

0

Left

1

Center

2

Right

3

Left Surround

4

Right Surround

Measurement Data

Scalar Values

These attributes contain a single value which is updated each time the meter processes new samples.

Tip

For convenience, all relevant measurement values can be accessed together through Meter.current_measurement. This returns an object containing the items listed above as well as other useful measurements.

See the CurrentMeasurement class reference for the full list.

Array Values

These attributes contain arrays of the measurement values from each processing iteration (every 100 ms).

When accessed, the arrays will contain the most recent data point in their last element. In other words, when a new 100ms chunk of input has been processed, the arrays will be one element larger (when accessed).

Structured Arrays

The historical Momentary Loudness and Short-Term Loudness measurements can be accessed as a structured array through Meter.block_data:

>>> ms_times = meter.block_data['t']           # A 1D array of times in seconds
>>> momentary_values = meter.block_data['m']   # A 1D array of momentary loudness values
>>> short_term_values = meter.block_data['s']  # A 1D array of short-term loudness values

The historical True Peak measurements can be accessed from Meter.true_peak_array which contains the peak values for each channel along with their measurement times:

>>> tp_times = meter.true_peak_array['t']   # A 1D array of times in seconds
>>> tp_values = meter.true_peak_array['tp'] # A 2D array of shape (n, num_channels)

Direct Access