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.
Index |
Name |
|---|---|
0 |
Left |
1 |
Right |
Index |
Name |
|---|---|
0 |
Left |
1 |
Center |
2 |
Right |
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.
Meter.integrated_lkfsholds the latest integrated loudness value.Meter.lraholds the latest loudness range value.Meter.true_peak_currentholds the latest true peak values for each channel.Meter.true_peak_maxholds the maximum true peak value observed across all channels.
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¶
Meter.tholds the times for momentary/short-term measurements.Meter.momentary_lkfsholds the historical momentary loudness values.Meter.short_term_lkfsholds the historical short-term loudness values.