Skip to content
Permalink
88bcc52240
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
2159 lines (1904 sloc) 67.3 KB
// Copyright (c) 2011 Jan Kokemüller
// Copyright (c) 2020 Sebastian Dröge <sebastian@centricular.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
use crate::energy_to_loudness;
use bitflags::bitflags;
use std::error;
use std::fmt;
/// Error values for [`EbuR128`](struct.EbuR128.html) functions.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Error {
/// Not enough memory
NoMem,
/// Invalid mode selected
InvalidMode,
/// Invalid channel index passed
InvalidChannelIndex,
}
impl error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::NoMem => write!(f, "NoMem"),
Error::InvalidMode => write!(f, "Invalid Mode"),
Error::InvalidChannelIndex => write!(f, "Invalid Channel Index"),
}
}
}
bitflags! {
/// Processing mode.
///
/// Use these values in [`EbuR128::new`](struct.EbuR128.html#method.new). Try to use the lowest
/// possible modes that suit your needs, as performance will be better.
pub struct Mode: u8 {
/// can call [`EbuR128::loudness_momentary`](struct.EbuR128.html#method.loudness_momentary)
const M = 0b00000001;
/// can call [`EbuR128::loudness_shortterm`](struct.EbuR128.html#method.loudness_shortterm)
const S = 0b00000010 | Mode::M.bits;
/// can call [`EbuR128::loudness_global`](struct.EbuR128.html#method.loudness_global) and
/// [`EbuR128::relative_threshold`](struct.EbuR128.html#method.relative_threshold)
const I = 0b00000100 | Mode::M.bits;
/// can call [`EbuR128::loudness_range`](struct.EbuR128.html#method.loudness_range)
const LRA = 0b00001000 | Mode::S.bits;
/// can call [`EbuR128::sample_peak`](struct.EbuR128.html#method.sample_peak)
const SAMPLE_PEAK = 0b00010000 | Mode::M.bits;
/// can call [`EbuR128::true_peak`](struct.EbuR128.html#method.true_peak)
const TRUE_PEAK = 0b00110001;
/// uses histogram algorithm to calculate loudness
const HISTOGRAM = 0b01000000;
}
}
/// Channel position.
///
/// Use these values when setting the channel map with
/// [`EbuR128::set_channel`](struct.EbuR128.html#method.set_channel).
/// See definitions in ITU R-REC-BS 1770-4.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum Channel {
/// unused channel (for example LFE channel)
Unused,
/// Left or itu M+030
Left,
/// Right or itu M-030
Right,
/// Center or itu M+000
Center,
/// Left surround or itu M+110
LeftSurround,
/// Right surround or itu M-110
RightSurround,
/// a channel that is counted twice
DualMono,
/// itu M+SC
MpSC,
/// itu M-SC
MmSC,
/// itu M+060
Mp060,
/// itu M-060
Mm060,
/// itu M+090
Mp090,
/// itu M-090
Mm090,
/// itu M+135
Mp135,
/// itu M-135
Mm135,
/// itu M+180
Mp180,
/// itu U+000
Up000,
/// itu U+030
Up030,
/// itu U-030
Um030,
/// itu U+045
Up045,
/// itu U-030
Um045,
/// itu U+090
Up090,
/// itu U-090
Um090,
/// itu U+110
Up110,
/// itu U-110
Um110,
/// itu U+135
Up135,
/// itu U-135
Um135,
/// itu U+180
Up180,
/// itu T+000
Tp000,
/// itu B+000
Bp000,
/// itu B+045
Bp045,
/// itu B-045
Bm045,
}
/// EBU R128 loudness analyzer.
pub struct EbuR128 {
/// The current mode.
mode: Mode,
/// The sample rate.
rate: u32,
/// The number of channels
channels: u32,
/// Filtered audio data (used as ring buffer).
audio_data: Box<[f64]>,
/// Current index for audio_data.
audio_data_index: usize,
/// How many frames are needed for a gating block. Will correspond to 400ms
/// of audio at initialization, and 100ms after the first block (75% overlap
/// as specified in the 2011 revision of BS1770).
needed_frames: usize,
/// The channel map. Has as many elements as there are channels.
channel_map: Box<[Channel]>,
/// How many samples fit in 100ms (rounded).
samples_in_100ms: usize,
/// Filter.
filter: crate::filter::Filter,
/// Block energy history.
block_energy_history: crate::history::History,
/// Short term block energy history.
short_term_block_energy_history: crate::history::History,
short_term_frame_counter: usize,
/// Maximum sample peak, one per channel.
sample_peak: Box<[f64]>,
/// Maximum true peak, one per channel.
true_peak: Box<[f64]>,
/// The maximum window duration in ms.
window: usize,
history: usize,
}
impl fmt::Debug for EbuR128 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EbuR128")
.field("mode", &self.mode)
.field("rate", &self.rate)
.field("channels", &self.channels)
// Not audio data
.field("audio_data_index", &self.audio_data_index)
.field("needed_frames", &self.needed_frames)
.field("channel_map", &self.channel_map)
.field("samples_in_100ms", &self.samples_in_100ms)
.field("filter", &self.filter)
.field("block_energy_history", &self.block_energy_history)
.field(
"short_term_block_energy_history",
&self.short_term_block_energy_history,
)
.field("short_term_frame_counter", &self.short_term_frame_counter)
.field("sample_peak", &self.sample_peak)
.field("true_peak", &self.true_peak)
.field("window", &self.window)
.field("history", &self.history)
.finish()
}
}
pub(crate) fn default_channel_map(channels: u32) -> Vec<Channel> {
match channels {
4 => vec![
Channel::Left,
Channel::Right,
Channel::LeftSurround,
Channel::RightSurround,
],
5 => vec![
Channel::Left,
Channel::Right,
Channel::Center,
Channel::LeftSurround,
Channel::RightSurround,
],
_ => {
let mut v = vec![Channel::Unused; channels as usize];
let set_channels = std::cmp::min(channels as usize, 6);
v[0..set_channels].copy_from_slice(
&[
Channel::Left,
Channel::Right,
Channel::Center,
Channel::Unused,
Channel::LeftSurround,
Channel::RightSurround,
][..set_channels],
);
v
}
}
}
const MAX_RATE: u32 = 2822400;
const MAX_CHANNELS: u32 = 64;
impl EbuR128 {
/// Allocate audio data buffer used by the filter and check if we can allocate enough memory
/// for it.
fn allocate_audio_data(channels: u32, rate: u32, window: usize) -> Result<Box<[f64]>, Error> {
let samples_in_100ms = (rate as usize + 5) / 10;
let mut audio_data_frames = (rate as usize).checked_mul(window).ok_or(Error::NoMem)? / 1000;
if audio_data_frames % samples_in_100ms != 0 {
// round up to multiple of samples_in_100ms
audio_data_frames = audio_data_frames
.checked_add(samples_in_100ms)
.ok_or(Error::NoMem)?
- (audio_data_frames % samples_in_100ms);
}
let audio_data = vec![
0.0;
audio_data_frames
.checked_mul(channels as usize)
.ok_or(Error::NoMem)?
]
.into_boxed_slice();
Ok(audio_data)
}
/// Create a new instance with the given configuration.
pub fn new(channels: u32, rate: u32, mode: Mode) -> Result<Self, Error> {
if channels == 0 || channels > MAX_CHANNELS {
return Err(Error::NoMem);
}
if rate < 16 || rate > MAX_RATE {
return Err(Error::NoMem);
}
let sample_peak = vec![0.0; channels as usize];
let true_peak = vec![0.0; channels as usize];
let history = std::usize::MAX;
let samples_in_100ms = (rate as usize + 5) / 10;
let window = if mode.contains(Mode::S) {
3000
} else if mode.contains(Mode::M) {
400
} else {
return Err(Error::InvalidMode);
};
let audio_data = Self::allocate_audio_data(channels, rate, window)?;
// start at the beginning of the buffer
let audio_data_index = 0;
let block_energy_history =
crate::history::History::new(mode.contains(Mode::HISTOGRAM), history / 100);
let short_term_block_energy_history =
crate::history::History::new(mode.contains(Mode::HISTOGRAM), history / 3000);
let short_term_frame_counter = 0;
let filter = crate::filter::Filter::new(
rate,
channels,
mode.contains(Mode::SAMPLE_PEAK),
mode.contains(Mode::TRUE_PEAK),
);
let channel_map = default_channel_map(channels);
// the first block needs 400ms of audio data
let needed_frames = samples_in_100ms * 4;
Ok(Self {
mode,
rate,
channels,
audio_data,
audio_data_index,
needed_frames,
channel_map: channel_map.into_boxed_slice(),
samples_in_100ms,
filter,
block_energy_history,
short_term_block_energy_history,
short_term_frame_counter,
sample_peak: sample_peak.into_boxed_slice(),
true_peak: true_peak.into_boxed_slice(),
window,
history,
})
}
/// Get the configured mode.
pub fn mode(&self) -> Mode {
self.mode
}
/// Get the configured number of channels.
pub fn channels(&self) -> u32 {
self.channels
}
/// Get the configured sample rate.
pub fn rate(&self) -> u32 {
self.rate
}
/// Get the configured channel types.
pub fn channel_map(&self) -> &[Channel] {
&*self.channel_map
}
/// Get the configured maximum window duration in ms.
pub fn max_window(&self) -> usize {
self.window
}
/// Get the configured maximum history in ms.
pub fn max_history(&self) -> usize {
self.history
}
/// Set channel type.
///
/// The default is:
///
/// * 0 \-> `Left`
/// * 1 \-> `Right`
/// * 2 \-> `Center`
/// * 3 \-> `Unused`
/// * 4 \-> `LeftSurround`
/// * 5 \-> `RightSurround`
/// * _ \-> `Unused`
pub fn set_channel(&mut self, channel_number: u32, value: Channel) -> Result<(), Error> {
if channel_number >= self.channels {
return Err(Error::InvalidChannelIndex);
}
if value == Channel::DualMono && (self.channels != 1 || channel_number != 0) {
return Err(Error::InvalidChannelIndex);
}
self.channel_map[channel_number as usize] = value;
Ok(())
}
/// Set channel types.
///
/// The default is:
///
/// * 0 \-> `Left`
/// * 1 \-> `Right`
/// * 2 \-> `Center`
/// * 3 \-> `Unused`
/// * 4 \-> `LeftSurround`
/// * 5 \-> `RightSurround`
/// * _ \-> `Unused`
pub fn set_channel_map(&mut self, channel_map: &[Channel]) -> Result<(), Error> {
if channel_map.len() != self.channels as usize {
return Err(Error::InvalidChannelIndex);
}
for (channel_number, value) in channel_map.iter().enumerate() {
if *value == Channel::DualMono && (self.channels != 1 || channel_number != 0) {
return Err(Error::InvalidChannelIndex);
}
}
self.channel_map.copy_from_slice(channel_map);
Ok(())
}
/// Change library parameters.
///
/// Note that the channel map will be reset when setting a different number of channels. The
/// current unfinished block will be lost.
pub fn change_parameters(&mut self, channels: u32, rate: u32) -> Result<(), Error> {
if channels == 0 || channels > MAX_CHANNELS {
return Err(Error::NoMem);
}
if rate < 16 || rate > MAX_RATE {
return Err(Error::NoMem);
}
if self.rate == rate && self.channels == channels {
return Ok(());
}
self.audio_data = Self::allocate_audio_data(channels, rate, self.window)?;
if self.channels != channels {
self.channels = channels;
self.channel_map = default_channel_map(channels).into_boxed_slice();
self.sample_peak = vec![0.0; channels as usize].into_boxed_slice();
self.true_peak = vec![0.0; channels as usize].into_boxed_slice();
}
if self.rate != rate {
self.rate = rate;
self.samples_in_100ms = (rate as usize + 5) / 10;
}
self.filter = crate::filter::Filter::new(
rate,
channels,
self.mode.contains(Mode::SAMPLE_PEAK),
self.mode.contains(Mode::TRUE_PEAK),
);
// the first block needs 400ms of audio data
self.needed_frames = self.samples_in_100ms * 4;
// start at the beginning of the buffer
self.audio_data_index = 0;
// reset short term frame counter
self.short_term_frame_counter = 0;
Ok(())
}
/// Set the maximum window duration.
///
/// Set the maximum duration in ms that will be used for
/// [`EbuR128::loudness_window`](struct.EbuR128.html#method.loudness_window). Note that this
/// destroys the current content of the audio buffer.
pub fn set_max_window(&mut self, window: u32) -> Result<(), Error> {
let window = if self.mode.contains(Mode::S) {
std::cmp::max(window, 3000)
} else if self.mode.contains(Mode::M) {
std::cmp::max(window, 400)
} else {
window
};
if window as usize == self.window {
return Ok(());
}
self.audio_data = Self::allocate_audio_data(self.channels, self.rate, window as usize)?;
self.window = window as usize;
// the first block needs 400ms of audio data
self.needed_frames = self.samples_in_100ms * 4;
// start at the beginning of the buffer
self.audio_data_index = 0;
// reset short term frame counter
self.short_term_frame_counter = 0;
Ok(())
}
/// Set the maximum history.
///
/// Set the maximum history in ms that will be stored for loudness integration. More history
/// provides more accurate results, but requires more resources.
///
/// Applies to [`EbuR128::loudness_range`](struct.EbuR128.html#method.loudness_range) and
/// [`EbuR128::loudness_global`](struct.EbuR128.html#method.loudness_global) when
/// `Mode::HISTOGRAM` is not set.
///
/// Default is `ULONG_MAX` (at least ~50 days). Minimum is 3000ms for `Mode::LRA` and 400ms
/// for `Mode::M`.
pub fn set_max_history(&mut self, history: u32) -> Result<(), Error> {
let history = if self.mode.contains(Mode::S) {
std::cmp::max(history, 3000)
} else if self.mode.contains(Mode::M) {
std::cmp::max(history, 400)
} else {
history
};
if self.history == history as usize {
return Ok(());
}
self.history = history as usize;
self.block_energy_history.set_max_size(self.history / 100);
self.short_term_block_energy_history
.set_max_size(self.history / 3000);
Ok(())
}
/// Resets the current state.
pub fn reset(&mut self) {
// TODO: Use slice::fill() once stabilized
for v in &mut *self.audio_data {
*v = 0.0;
}
// the first block needs 400ms of audio data
self.needed_frames = self.samples_in_100ms * 4;
// start at the beginning of the buffer
self.audio_data_index = 0;
// reset short term frame counter
self.short_term_frame_counter = 0;
// TODO: Use slice::fill() once stabilized
for v in &mut *self.true_peak {
*v = 0.0;
}
// TODO: Use slice::fill() once stabilized
for v in &mut *self.sample_peak {
*v = 0.0;
}
self.filter.reset();
self.block_energy_history.reset();
self.short_term_block_energy_history.reset();
}
/// Process frames. This is the generic variant of the different public add_frames() functions
/// that are defined below.
fn add_frames<'a, T: crate::AsF64 + 'a, S: crate::Samples<'a, T>>(
&mut self,
mut src: S,
) -> Result<(), Error> {
if src.frames() == 0 {
return Ok(());
}
if self.channels == 0 {
return Err(Error::NoMem);
}
self.filter.reset_peaks();
while src.frames() > 0 {
let num_frames = src.frames();
if num_frames >= self.needed_frames {
let (current, next) = src.split_at(self.needed_frames);
self.filter.process(
&current,
&mut *self.audio_data,
self.audio_data_index,
&self.channel_map,
);
src = next;
self.audio_data_index += self.needed_frames;
if self.mode.contains(Mode::I) {
let energy = crate::filter::Filter::calc_gating_block(
self.samples_in_100ms * 4,
&self.audio_data,
self.audio_data_index,
&self.channel_map,
);
self.block_energy_history.add(energy);
}
if self.mode.contains(Mode::LRA) {
self.short_term_frame_counter += self.needed_frames;
if self.short_term_frame_counter == self.samples_in_100ms * 30 {
let energy = self.energy_shortterm()?;
self.short_term_block_energy_history.add(energy);
self.short_term_frame_counter = self.samples_in_100ms * 20;
}
}
if self.audio_data_index == self.audio_data.len() / self.channels as usize {
self.audio_data_index = 0;
}
// 100ms are needed for all blocks besides the first one
self.needed_frames = self.samples_in_100ms;
} else {
let (current, next) = src.split_at(num_frames);
self.filter.process(
&current,
&mut *self.audio_data,
self.audio_data_index,
&self.channel_map,
);
self.audio_data_index += num_frames;
if self.mode.contains(Mode::LRA) {
self.short_term_frame_counter += num_frames;
}
src = next;
self.needed_frames -= num_frames;
}
}
let prev_sample_peak = self.filter.sample_peak();
for (sample_peak, prev_sample_peak) in
self.sample_peak.iter_mut().zip(prev_sample_peak.iter())
{
if *prev_sample_peak > *sample_peak {
*sample_peak = *prev_sample_peak;
}
}
let prev_true_peak = self.filter.true_peak();
for (true_peak, prev_true_peak) in self.true_peak.iter_mut().zip(prev_true_peak.iter()) {
if *prev_true_peak > *true_peak {
*true_peak = *prev_true_peak;
}
}
Ok(())
}
/// Add interleaved frames to be processed.
pub fn add_frames_i16(&mut self, frames: &[i16]) -> Result<(), Error> {
self.add_frames(crate::Interleaved::new(frames, self.channels as usize)?)
}
/// Add interleaved frames to be processed.
pub fn add_frames_i32(&mut self, frames: &[i32]) -> Result<(), Error> {
self.add_frames(crate::Interleaved::new(frames, self.channels as usize)?)
}
/// Add interleaved frames to be processed.
pub fn add_frames_f32(&mut self, frames: &[f32]) -> Result<(), Error> {
self.add_frames(crate::Interleaved::new(frames, self.channels as usize)?)
}
/// Add interleaved frames to be processed.
pub fn add_frames_f64(&mut self, frames: &[f64]) -> Result<(), Error> {
self.add_frames(crate::Interleaved::new(frames, self.channels as usize)?)
}
/// Add planar frames to be processed.
pub fn add_frames_planar_i16(&mut self, frames: &[&[i16]]) -> Result<(), Error> {
self.add_frames(crate::Planar::new(frames)?)
}
/// Add planar frames to be processed.
pub fn add_frames_planar_i32(&mut self, frames: &[&[i32]]) -> Result<(), Error> {
self.add_frames(crate::Planar::new(frames)?)
}
/// Add planar frames to be processed.
pub fn add_frames_planar_f32(&mut self, frames: &[&[f32]]) -> Result<(), Error> {
self.add_frames(crate::Planar::new(frames)?)
}
/// Add planar frames to be processed.
pub fn add_frames_planar_f64(&mut self, frames: &[&[f64]]) -> Result<(), Error> {
self.add_frames(crate::Planar::new(frames)?)
}
/// Get global integrated loudness in LUFS.
pub fn loudness_global(&self) -> Result<f64, Error> {
if !self.mode.contains(Mode::I) {
return Err(Error::InvalidMode);
}
Ok(self.block_energy_history.gated_loudness())
}
/// Get global integrated loudness in LUFS across multiple instances.
// FIXME: Should maybe be IntoIterator? Maybe AsRef<Self>?
pub fn loudness_global_multiple<'a>(
iter: impl Iterator<Item = &'a Self>,
) -> Result<f64, Error> {
use smallvec::SmallVec;
let h = iter
.map(|e| {
if !e.mode.contains(Mode::I) {
Err(Error::InvalidMode)
} else {
Ok(&e.block_energy_history)
}
})
.collect::<Result<SmallVec<[_; 16]>, _>>()?;
Ok(crate::history::History::gated_loudness_multiple(&*h))
}
fn energy_in_interval(&self, interval_frames: usize) -> Result<f64, Error> {
if interval_frames > self.audio_data.len() / self.channels as usize {
return Err(Error::InvalidMode);
}
Ok(crate::filter::Filter::calc_gating_block(
interval_frames,
&self.audio_data,
self.audio_data_index,
&self.channel_map,
))
}
/// Get momentary loudness (last 400ms) in LUFS.
pub fn loudness_momentary(&self) -> Result<f64, Error> {
let energy = self.energy_in_interval(self.samples_in_100ms * 4)?;
if energy <= 0.0 {
return Ok(-std::f64::INFINITY);
}
Ok(energy_to_loudness(energy))
}
fn energy_shortterm(&self) -> Result<f64, Error> {
self.energy_in_interval(self.samples_in_100ms * 30)
}
/// Get short-term loudness (last 3s) in LUFS.
pub fn loudness_shortterm(&self) -> Result<f64, Error> {
let energy = self.energy_shortterm()?;
if energy <= 0.0 {
return Ok(-std::f64::INFINITY);
}
Ok(energy_to_loudness(energy))
}
/// Get loudness of the specified window in LUFS.
///
/// window must not be larger than the current window. The current window can be changed by
/// calling [`EbuR128::set_max_window`](struct.EbuR128.html#method.set_max_window).
pub fn loudness_window(&self, window: u32) -> Result<f64, Error> {
let interval_frames = (self.rate as usize)
.checked_mul(window as usize)
.ok_or(Error::InvalidMode)?
/ 1000;
let energy = self.energy_in_interval(interval_frames)?;
if energy <= 0.0 {
return Ok(-std::f64::INFINITY);
}
Ok(energy_to_loudness(energy))
}
/// Get loudness range (LRA) of programme in LU.
///
/// Calculates loudness range according to EBU 3342.
pub fn loudness_range(&self) -> Result<f64, Error> {
if !self.mode.contains(Mode::LRA) {
return Err(Error::InvalidMode);
}
Ok(self.short_term_block_energy_history.loudness_range())
}
/// Get loudness range (LRA) of programme in LU across multiple instances.
///
/// Calculates loudness range according to EBU 3342.
// FIXME: Should maybe be IntoIterator? Maybe AsRef<Self>?
pub fn loudness_range_multiple<'a>(
iter: impl IntoIterator<Item = &'a Self>,
) -> Result<f64, Error> {
use smallvec::SmallVec;
let h = iter
.into_iter()
.map(|e| {
if !e.mode.contains(Mode::LRA) {
Err(Error::InvalidMode)
} else {
Ok(&e.short_term_block_energy_history)
}
})
.collect::<Result<SmallVec<[_; 16]>, _>>()?;
crate::history::History::loudness_range_multiple(&*h).map_err(|_| Error::InvalidMode)
}
/// Get maximum sample peak from all frames that have been processed.
///
/// The equation to convert to dBFS is: 20 * log10(out)
pub fn sample_peak(&self, channel_number: u32) -> Result<f64, Error> {
if !self.mode.contains(Mode::SAMPLE_PEAK) {
return Err(Error::InvalidMode);
}
if channel_number >= self.channels {
return Err(Error::InvalidChannelIndex);
}
Ok(self.sample_peak[channel_number as usize])
}
/// Get maximum sample peak from the last call to
/// [`EbuR128::add_frames`](struct.EbuR128.html#method.add_frames_i16).
///
/// The equation to convert to dBFS is: 20 * log10(out)
pub fn prev_sample_peak(&self, channel_number: u32) -> Result<f64, Error> {
if !self.mode.contains(Mode::SAMPLE_PEAK) {
return Err(Error::InvalidMode);
}
if channel_number >= self.channels {
return Err(Error::InvalidChannelIndex);
}
Ok(self.filter.sample_peak()[channel_number as usize])
}
/// Get maximum true peak from all frames that have been processed.
///
/// Uses an implementation defined algorithm to calculate the true peak. Do not try to compare
/// resulting values across different versions of the library, as the algorithm may change.
///
/// The current implementation uses a custom polyphase FIR interpolator to calculate true peak.
/// Will oversample 4x for sample rates < 96000 Hz, 2x for sample rates < 192000 Hz and leave
/// the signal unchanged for 192000 Hz.
///
/// The equation to convert to dBTP is: 20 * log10(out)
pub fn true_peak(&self, channel_number: u32) -> Result<f64, Error> {
if !self.mode.contains(Mode::TRUE_PEAK) {
return Err(Error::InvalidMode);
}
if channel_number >= self.channels {
return Err(Error::InvalidChannelIndex);
}
if self.sample_peak[channel_number as usize] > self.true_peak[channel_number as usize] {
Ok(self.sample_peak[channel_number as usize])
} else {
Ok(self.true_peak[channel_number as usize])
}
}
/// Get maximum true peak from the last call to
/// [`EbuR128::add_frames`](struct.EbuR128.html#method.add_frames_i16).
///
/// Uses an implementation defined algorithm to calculate the true peak. Do not try to compare
/// resulting values across different versions of the library, as the algorithm may change.
///
/// The current implementation uses a custom polyphase FIR interpolator to calculate true peak.
/// Will oversample 4x for sample rates < 96000 Hz, 2x for sample rates < 192000 Hz and leave
/// the signal unchanged for 192000 Hz.
///
/// The equation to convert to dBTP is: 20 * log10(out)
pub fn prev_true_peak(&self, channel_number: u32) -> Result<f64, Error> {
if !self.mode.contains(Mode::TRUE_PEAK) {
return Err(Error::InvalidMode);
}
if channel_number >= self.channels {
return Err(Error::InvalidChannelIndex);
}
let sample_peak = self.filter.sample_peak();
let true_peak = self.filter.true_peak();
if sample_peak[channel_number as usize] > true_peak[channel_number as usize] {
Ok(sample_peak[channel_number as usize])
} else {
Ok(true_peak[channel_number as usize])
}
}
/// Get relative threshold in LUFS.
pub fn relative_threshold(&self) -> Result<f64, Error> {
if !self.mode.contains(Mode::I) {
return Err(Error::InvalidMode);
}
Ok(self.block_energy_history.relative_threshold())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "c-tests")]
use crate::tests::Signal;
use float_eq::assert_float_eq;
#[cfg(feature = "c-tests")]
use quickcheck_macros::quickcheck;
#[test]
fn sine_stereo_i16() {
let mut data = vec![0i16; 48_000 * 5 * 2];
let mut accumulator = 0.0;
let step = 2.0 * std::f32::consts::PI * 440.0 / 48_000.0;
for out in data.chunks_exact_mut(2) {
let val = f32::sin(accumulator) * (std::i16::MAX - 1) as f32;
out[0] = val as i16;
out[1] = val as i16;
accumulator += step;
}
let mut ebu = EbuR128::new(2, 48_000, Mode::all()).unwrap();
ebu.add_frames_i16(&data).unwrap();
assert_float_eq!(
ebu.loudness_global().unwrap(),
-0.6500000000000054,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_momentary().unwrap(),
-0.6820309226891973,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_shortterm().unwrap(),
-0.6834583474398446,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_window(1).unwrap(),
-0.875007988101488,
abs <= 0.000001
);
assert_float_eq!(ebu.loudness_range().unwrap(), 0.0, abs <= 0.000001);
assert_float_eq!(
ebu.sample_peak(0).unwrap(),
0.99993896484375,
abs <= 0.000001
);
assert_float_eq!(
ebu.sample_peak(1).unwrap(),
0.99993896484375,
abs <= 0.000001
);
assert_float_eq!(
ebu.prev_sample_peak(0).unwrap(),
0.99993896484375,
abs <= 0.000001
);
assert_float_eq!(
ebu.prev_sample_peak(1).unwrap(),
0.99993896484375,
abs <= 0.000001
);
assert_float_eq!(
ebu.true_peak(0).unwrap(),
1.0007814168930054,
abs <= 0.000001
);
assert_float_eq!(
ebu.true_peak(1).unwrap(),
1.0007814168930054,
abs <= 0.000001
);
assert_float_eq!(
ebu.prev_true_peak(0).unwrap(),
1.0007814168930054,
abs <= 0.000001
);
assert_float_eq!(
ebu.prev_true_peak(1).unwrap(),
1.0007814168930054,
abs <= 0.000001
);
assert_float_eq!(
ebu.relative_threshold().unwrap(),
-10.650000000000006,
abs <= 0.000001
);
ebu.reset();
assert_float_eq!(
ebu.loudness_global().unwrap(),
-std::f64::INFINITY,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_momentary().unwrap(),
-std::f64::INFINITY,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_shortterm().unwrap(),
-std::f64::INFINITY,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_window(1).unwrap(),
-std::f64::INFINITY,
abs <= 0.000001
);
assert_float_eq!(ebu.loudness_range().unwrap(), 0.0, abs <= 0.000001);
assert_float_eq!(ebu.sample_peak(0).unwrap(), 0.0, abs <= 0.000001);
assert_float_eq!(ebu.sample_peak(1).unwrap(), 0.0, abs <= 0.000001);
assert_float_eq!(ebu.prev_sample_peak(0).unwrap(), 0.0, abs <= 0.000001);
assert_float_eq!(ebu.prev_sample_peak(1).unwrap(), 0.0, abs <= 0.000001);
assert_float_eq!(ebu.true_peak(0).unwrap(), 0.0, abs <= 0.000001);
assert_float_eq!(ebu.true_peak(1).unwrap(), 0.0, abs <= 0.000001);
assert_float_eq!(ebu.prev_true_peak(0).unwrap(), 0.0, abs <= 0.000001);
assert_float_eq!(ebu.prev_true_peak(1).unwrap(), 0.0, abs <= 0.000001);
assert_float_eq!(ebu.relative_threshold().unwrap(), -70.0, abs <= 0.000001);
}
#[test]
fn sine_stereo_i32() {
let mut data = vec![0i32; 48_000 * 5 * 2];
let mut accumulator = 0.0;
let step = 2.0 * std::f32::consts::PI * 440.0 / 48_000.0;
for out in data.chunks_exact_mut(2) {
let val = f32::sin(accumulator) * (std::i32::MAX - 1) as f32;
out[0] = val as i32;
out[1] = val as i32;
accumulator += step;
}
let mut ebu = EbuR128::new(2, 48_000, Mode::all()).unwrap();
ebu.add_frames_i32(&data).unwrap();
assert_float_eq!(
ebu.loudness_global().unwrap(),
-0.6500000000000054,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_momentary().unwrap(),
-0.6813325598274425,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_shortterm().unwrap(),
-0.6827591715105212,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_window(1).unwrap(),
-0.8742956620040943,
abs <= 0.000001
);
assert_float_eq!(ebu.loudness_range().unwrap(), 0.0, abs <= 0.000001);
assert_float_eq!(ebu.sample_peak(0).unwrap(), 1.0, abs <= 0.000001);
assert_float_eq!(ebu.sample_peak(1).unwrap(), 1.0, abs <= 0.000001);
assert_float_eq!(ebu.prev_sample_peak(0).unwrap(), 1.0, abs <= 0.000001);
assert_float_eq!(ebu.prev_sample_peak(1).unwrap(), 1.0, abs <= 0.000001);
assert_float_eq!(
ebu.true_peak(0).unwrap(),
1.0008491277694702,
abs <= 0.000001
);
assert_float_eq!(
ebu.true_peak(1).unwrap(),
1.0008491277694702,
abs <= 0.000001
);
assert_float_eq!(
ebu.prev_true_peak(0).unwrap(),
1.0008491277694702,
abs <= 0.000001
);
assert_float_eq!(
ebu.prev_true_peak(1).unwrap(),
1.0008491277694702,
abs <= 0.000001
);
assert_float_eq!(
ebu.relative_threshold().unwrap(),
-10.650000000000006,
abs <= 0.000001
);
}
#[test]
fn sine_stereo_f32() {
let mut data = vec![0.0f32; 48_000 * 5 * 2];
let mut accumulator = 0.0;
let step = 2.0 * std::f32::consts::PI * 440.0 / 48_000.0;
for out in data.chunks_exact_mut(2) {
let val = f32::sin(accumulator);
out[0] = val;
out[1] = val;
accumulator += step;
}
let mut ebu = EbuR128::new(2, 48_000, Mode::all()).unwrap();
ebu.add_frames_f32(&data).unwrap();
assert_float_eq!(
ebu.loudness_global().unwrap(),
-0.6500000000000054,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_momentary().unwrap(),
-0.6813325598268921,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_shortterm().unwrap(),
-0.6827591715100236,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_window(1).unwrap(),
-0.8742956620008693,
abs <= 0.000001
);
assert_float_eq!(ebu.loudness_range().unwrap(), 0.0, abs <= 0.000001);
assert_float_eq!(ebu.sample_peak(0).unwrap(), 1.0, abs <= 0.000001);
assert_float_eq!(ebu.sample_peak(1).unwrap(), 1.0, abs <= 0.000001);
assert_float_eq!(ebu.prev_sample_peak(0).unwrap(), 1.0, abs <= 0.000001);
assert_float_eq!(ebu.prev_sample_peak(1).unwrap(), 1.0, abs <= 0.000001);
assert_float_eq!(
ebu.true_peak(0).unwrap(),
1.0008491277694702,
abs <= 0.000001
);
assert_float_eq!(
ebu.true_peak(1).unwrap(),
1.0008491277694702,
abs <= 0.000001
);
assert_float_eq!(
ebu.prev_true_peak(0).unwrap(),
1.0008491277694702,
abs <= 0.000001
);
assert_float_eq!(
ebu.prev_true_peak(1).unwrap(),
1.0008491277694702,
abs <= 0.000001
);
assert_float_eq!(
ebu.relative_threshold().unwrap(),
-10.650000000000006,
abs <= 0.000001
);
}
#[test]
fn sine_stereo_f64() {
let mut data = vec![0.0f64; 48_000 * 5 * 2];
let mut accumulator = 0.0;
let step = 2.0 * std::f32::consts::PI * 440.0 / 48_000.0;
for out in data.chunks_exact_mut(2) {
let val = f32::sin(accumulator);
out[0] = val as f64;
out[1] = val as f64;
accumulator += step;
}
let mut ebu = EbuR128::new(2, 48_000, Mode::all()).unwrap();
ebu.add_frames_f64(&data).unwrap();
assert_float_eq!(
ebu.loudness_global().unwrap(),
-0.6500000000000054,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_momentary().unwrap(),
-0.6813325598268921,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_shortterm().unwrap(),
-0.6827591715100236,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_window(1).unwrap(),
-0.8742956620008693,
abs <= 0.000001
);
assert_float_eq!(ebu.loudness_range().unwrap(), 0.0, abs <= 0.000001);
assert_float_eq!(ebu.sample_peak(0).unwrap(), 1.0, abs <= 0.000001);
assert_float_eq!(ebu.sample_peak(1).unwrap(), 1.0, abs <= 0.000001);
assert_float_eq!(ebu.prev_sample_peak(0).unwrap(), 1.0, abs <= 0.000001);
assert_float_eq!(ebu.prev_sample_peak(1).unwrap(), 1.0, abs <= 0.000001);
assert_float_eq!(
ebu.true_peak(0).unwrap(),
1.0008491277694702,
abs <= 0.000001
);
assert_float_eq!(
ebu.true_peak(1).unwrap(),
1.0008491277694702,
abs <= 0.000001
);
assert_float_eq!(
ebu.prev_true_peak(0).unwrap(),
1.0008491277694702,
abs <= 0.000001
);
assert_float_eq!(
ebu.prev_true_peak(1).unwrap(),
1.0008491277694702,
abs <= 0.000001
);
assert_float_eq!(
ebu.relative_threshold().unwrap(),
-10.650000000000006,
abs <= 0.000001
);
}
#[test]
fn sine_stereo_i16_no_histogram() {
let mut data = vec![0i16; 48_000 * 5 * 2];
let mut accumulator = 0.0;
let step = 2.0 * std::f32::consts::PI * 440.0 / 48_000.0;
for out in data.chunks_exact_mut(2) {
let val = f32::sin(accumulator) * (std::i16::MAX - 1) as f32;
out[0] = val as i16;
out[1] = val as i16;
accumulator += step;
}
let mut ebu = EbuR128::new(2, 48_000, Mode::all() & !Mode::HISTOGRAM).unwrap();
ebu.add_frames_i16(&data).unwrap();
assert_float_eq!(
ebu.loudness_global().unwrap(),
-0.683303243667768,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_momentary().unwrap(),
-0.6820309226891973,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_shortterm().unwrap(),
-0.6834583474398446,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_window(1).unwrap(),
-0.875007988101488,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_range().unwrap(),
0.00006950793233284625,
abs <= 0.000001
);
assert_float_eq!(
ebu.sample_peak(0).unwrap(),
0.99993896484375,
abs <= 0.000001
);
assert_float_eq!(
ebu.sample_peak(1).unwrap(),
0.99993896484375,
abs <= 0.000001
);
assert_float_eq!(
ebu.prev_sample_peak(0).unwrap(),
0.99993896484375,
abs <= 0.000001
);
assert_float_eq!(
ebu.prev_sample_peak(1).unwrap(),
0.99993896484375,
abs <= 0.000001
);
assert_float_eq!(
ebu.true_peak(0).unwrap(),
1.0007814168930054,
abs <= 0.000001
);
assert_float_eq!(
ebu.true_peak(1).unwrap(),
1.0007814168930054,
abs <= 0.000001
);
assert_float_eq!(
ebu.prev_true_peak(0).unwrap(),
1.0007814168930054,
abs <= 0.000001
);
assert_float_eq!(
ebu.prev_true_peak(1).unwrap(),
1.0007814168930054,
abs <= 0.000001
);
assert_float_eq!(
ebu.relative_threshold().unwrap(),
-10.683303243667767,
abs <= 0.000001
);
}
#[test]
fn sine_stereo_i32_no_histogram() {
let mut data = vec![0i32; 48_000 * 5 * 2];
let mut accumulator = 0.0;
let step = 2.0 * std::f32::consts::PI * 440.0 / 48_000.0;
for out in data.chunks_exact_mut(2) {
let val = f32::sin(accumulator) * (std::i32::MAX - 1) as f32;
out[0] = val as i32;
out[1] = val as i32;
accumulator += step;
}
let mut ebu = EbuR128::new(2, 48_000, Mode::all() & !Mode::HISTOGRAM).unwrap();
ebu.add_frames_i32(&data).unwrap();
assert_float_eq!(
ebu.loudness_global().unwrap(),
-0.6826039914171368,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_momentary().unwrap(),
-0.6813325598274425,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_shortterm().unwrap(),
-0.6827591715105212,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_window(1).unwrap(),
-0.8742956620040943,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_range().unwrap(),
0.00006921150165073442,
abs <= 0.000001
);
assert_float_eq!(ebu.sample_peak(0).unwrap(), 1.0, abs <= 0.000001);
assert_float_eq!(ebu.sample_peak(1).unwrap(), 1.0, abs <= 0.000001);
assert_float_eq!(ebu.prev_sample_peak(0).unwrap(), 1.0, abs <= 0.000001);
assert_float_eq!(ebu.prev_sample_peak(1).unwrap(), 1.0, abs <= 0.000001);
assert_float_eq!(
ebu.true_peak(0).unwrap(),
1.0008491277694702,
abs <= 0.000001
);
assert_float_eq!(
ebu.true_peak(1).unwrap(),
1.0008491277694702,
abs <= 0.000001
);
assert_float_eq!(
ebu.prev_true_peak(0).unwrap(),
1.0008491277694702,
abs <= 0.000001
);
assert_float_eq!(
ebu.prev_true_peak(1).unwrap(),
1.0008491277694702,
abs <= 0.000001
);
assert_float_eq!(
ebu.relative_threshold().unwrap(),
-10.682603991417135,
abs <= 0.000001
);
}
#[test]
fn sine_stereo_f32_no_histogram() {
let mut data = vec![0.0f32; 48_000 * 5 * 2];
let mut accumulator = 0.0;
let step = 2.0 * std::f32::consts::PI * 440.0 / 48_000.0;
for out in data.chunks_exact_mut(2) {
let val = f32::sin(accumulator);
out[0] = val;
out[1] = val;
accumulator += step;
}
let mut ebu = EbuR128::new(2, 48_000, Mode::all() & !Mode::HISTOGRAM).unwrap();
ebu.add_frames_f32(&data).unwrap();
assert_float_eq!(
ebu.loudness_global().unwrap(),
-0.6826039914165554,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_momentary().unwrap(),
-0.6813325598268921,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_shortterm().unwrap(),
-0.6827591715100236,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_window(1).unwrap(),
-0.8742956620008693,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_range().unwrap(),
0.00006921150169403312,
abs <= 0.000001
);
assert_float_eq!(ebu.sample_peak(0).unwrap(), 1.0, abs <= 0.000001);
assert_float_eq!(ebu.sample_peak(1).unwrap(), 1.0, abs <= 0.000001);
assert_float_eq!(ebu.prev_sample_peak(0).unwrap(), 1.0, abs <= 0.000001);
assert_float_eq!(ebu.prev_sample_peak(1).unwrap(), 1.0, abs <= 0.000001);
assert_float_eq!(
ebu.true_peak(0).unwrap(),
1.0008491277694702,
abs <= 0.000001
);
assert_float_eq!(
ebu.true_peak(1).unwrap(),
1.0008491277694702,
abs <= 0.000001
);
assert_float_eq!(
ebu.prev_true_peak(0).unwrap(),
1.0008491277694702,
abs <= 0.000001
);
assert_float_eq!(
ebu.prev_true_peak(1).unwrap(),
1.0008491277694702,
abs <= 0.000001
);
assert_float_eq!(
ebu.relative_threshold().unwrap(),
-10.682603991416554,
abs <= 0.000001
);
}
#[test]
fn sine_stereo_f64_no_histogram() {
let mut data = vec![0.0f64; 48_000 * 5 * 2];
let mut accumulator = 0.0;
let step = 2.0 * std::f32::consts::PI * 440.0 / 48_000.0;
for out in data.chunks_exact_mut(2) {
let val = f32::sin(accumulator);
out[0] = val as f64;
out[1] = val as f64;
accumulator += step;
}
let mut ebu = EbuR128::new(2, 48_000, Mode::all() & !Mode::HISTOGRAM).unwrap();
ebu.add_frames_f64(&data).unwrap();
assert_float_eq!(
ebu.loudness_global().unwrap(),
-0.6826039914165554,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_momentary().unwrap(),
-0.6813325598268921,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_shortterm().unwrap(),
-0.6827591715100236,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_window(1).unwrap(),
-0.8742956620008693,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_range().unwrap(),
0.00006921150169403312,
abs <= 0.000001
);
assert_float_eq!(ebu.sample_peak(0).unwrap(), 1.0, abs <= 0.000001);
assert_float_eq!(ebu.sample_peak(1).unwrap(), 1.0, abs <= 0.000001);
assert_float_eq!(ebu.prev_sample_peak(0).unwrap(), 1.0, abs <= 0.000001);
assert_float_eq!(ebu.prev_sample_peak(1).unwrap(), 1.0, abs <= 0.000001);
assert_float_eq!(
ebu.true_peak(0).unwrap(),
1.0008491277694702,
abs <= 0.000001
);
assert_float_eq!(
ebu.true_peak(1).unwrap(),
1.0008491277694702,
abs <= 0.000001
);
assert_float_eq!(
ebu.prev_true_peak(0).unwrap(),
1.0008491277694702,
abs <= 0.000001
);
assert_float_eq!(
ebu.prev_true_peak(1).unwrap(),
1.0008491277694702,
abs <= 0.000001
);
assert_float_eq!(
ebu.relative_threshold().unwrap(),
-10.682603991416554,
abs <= 0.000001
);
}
#[test]
fn sine_stereo_i16_planar_no_histogram() {
let mut data = vec![0i16; 48_000 * 5 * 2];
let mut accumulator = 0.0;
let step = 2.0 * std::f32::consts::PI * 440.0 / 48_000.0;
let (fst, snd) = data.split_at_mut(48_000 * 5);
for (fst, snd) in fst.iter_mut().zip(snd.iter_mut()) {
let val = f32::sin(accumulator) * (std::i16::MAX - 1) as f32;
*fst = val as i16;
*snd = val as i16;
accumulator += step;
}
let mut ebu = EbuR128::new(2, 48_000, Mode::all() & !Mode::HISTOGRAM).unwrap();
ebu.add_frames_planar_i16(&[fst, snd]).unwrap();
assert_float_eq!(
ebu.loudness_global().unwrap(),
-0.683303243667768,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_momentary().unwrap(),
-0.6820309226891973,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_shortterm().unwrap(),
-0.6834583474398446,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_window(1).unwrap(),
-0.875007988101488,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_range().unwrap(),
0.00006950793233284625,
abs <= 0.000001
);
assert_float_eq!(
ebu.sample_peak(0).unwrap(),
0.99993896484375,
abs <= 0.000001
);
assert_float_eq!(
ebu.sample_peak(1).unwrap(),
0.99993896484375,
abs <= 0.000001
);
assert_float_eq!(
ebu.prev_sample_peak(0).unwrap(),
0.99993896484375,
abs <= 0.000001
);
assert_float_eq!(
ebu.prev_sample_peak(1).unwrap(),
0.99993896484375,
abs <= 0.000001
);
assert_float_eq!(
ebu.true_peak(0).unwrap(),
1.0007814168930054,
abs <= 0.000001
);
assert_float_eq!(
ebu.true_peak(1).unwrap(),
1.0007814168930054,
abs <= 0.000001
);
assert_float_eq!(
ebu.prev_true_peak(0).unwrap(),
1.0007814168930054,
abs <= 0.000001
);
assert_float_eq!(
ebu.prev_true_peak(1).unwrap(),
1.0007814168930054,
abs <= 0.000001
);
assert_float_eq!(
ebu.relative_threshold().unwrap(),
-10.683303243667767,
abs <= 0.000001
);
}
#[test]
fn sine_stereo_i32_planar_no_histogram() {
let mut data = vec![0i32; 48_000 * 5 * 2];
let mut accumulator = 0.0;
let step = 2.0 * std::f32::consts::PI * 440.0 / 48_000.0;
let (fst, snd) = data.split_at_mut(48_000 * 5);
for (fst, snd) in fst.iter_mut().zip(snd.iter_mut()) {
let val = f32::sin(accumulator) * (std::i32::MAX - 1) as f32;
*fst = val as i32;
*snd = val as i32;
accumulator += step;
}
let mut ebu = EbuR128::new(2, 48_000, Mode::all() & !Mode::HISTOGRAM).unwrap();
ebu.add_frames_planar_i32(&[fst, snd]).unwrap();
assert_float_eq!(
ebu.loudness_global().unwrap(),
-0.6826039914171368,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_momentary().unwrap(),
-0.6813325598274425,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_shortterm().unwrap(),
-0.6827591715105212,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_window(1).unwrap(),
-0.8742956620040943,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_range().unwrap(),
0.00006921150165073442,
abs <= 0.000001
);
assert_float_eq!(ebu.sample_peak(0).unwrap(), 1.0, abs <= 0.000001);
assert_float_eq!(ebu.sample_peak(1).unwrap(), 1.0, abs <= 0.000001);
assert_float_eq!(ebu.prev_sample_peak(0).unwrap(), 1.0, abs <= 0.000001);
assert_float_eq!(ebu.prev_sample_peak(1).unwrap(), 1.0, abs <= 0.000001);
assert_float_eq!(
ebu.true_peak(0).unwrap(),
1.0008491277694702,
abs <= 0.000001
);
assert_float_eq!(
ebu.true_peak(1).unwrap(),
1.0008491277694702,
abs <= 0.000001
);
assert_float_eq!(
ebu.prev_true_peak(0).unwrap(),
1.0008491277694702,
abs <= 0.000001
);
assert_float_eq!(
ebu.prev_true_peak(1).unwrap(),
1.0008491277694702,
abs <= 0.000001
);
assert_float_eq!(
ebu.relative_threshold().unwrap(),
-10.682603991417135,
abs <= 0.000001
);
}
#[test]
fn sine_stereo_f32_planar_no_histogram() {
let mut data = vec![0.0f32; 48_000 * 5 * 2];
let mut accumulator = 0.0;
let step = 2.0 * std::f32::consts::PI * 440.0 / 48_000.0;
let (fst, snd) = data.split_at_mut(48_000 * 5);
for (fst, snd) in fst.iter_mut().zip(snd.iter_mut()) {
let val = f32::sin(accumulator);
*fst = val;
*snd = val;
accumulator += step;
}
let mut ebu = EbuR128::new(2, 48_000, Mode::all() & !Mode::HISTOGRAM).unwrap();
ebu.add_frames_planar_f32(&[fst, snd]).unwrap();
assert_float_eq!(
ebu.loudness_global().unwrap(),
-0.6826039914165554,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_momentary().unwrap(),
-0.6813325598268921,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_shortterm().unwrap(),
-0.6827591715100236,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_window(1).unwrap(),
-0.8742956620008693,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_range().unwrap(),
0.00006921150169403312,
abs <= 0.000001
);
assert_float_eq!(ebu.sample_peak(0).unwrap(), 1.0, abs <= 0.000001);
assert_float_eq!(ebu.sample_peak(1).unwrap(), 1.0, abs <= 0.000001);
assert_float_eq!(ebu.prev_sample_peak(0).unwrap(), 1.0, abs <= 0.000001);
assert_float_eq!(ebu.prev_sample_peak(1).unwrap(), 1.0, abs <= 0.000001);
assert_float_eq!(
ebu.true_peak(0).unwrap(),
1.0008491277694702,
abs <= 0.000001
);
assert_float_eq!(
ebu.true_peak(1).unwrap(),
1.0008491277694702,
abs <= 0.000001
);
assert_float_eq!(
ebu.prev_true_peak(0).unwrap(),
1.0008491277694702,
abs <= 0.000001
);
assert_float_eq!(
ebu.prev_true_peak(1).unwrap(),
1.0008491277694702,
abs <= 0.000001
);
assert_float_eq!(
ebu.relative_threshold().unwrap(),
-10.682603991416554,
abs <= 0.000001
);
}
#[test]
fn sine_stereo_f64_planar_no_histogram() {
let mut data = vec![0.0f64; 48_000 * 5 * 2];
let mut accumulator = 0.0;
let step = 2.0 * std::f32::consts::PI * 440.0 / 48_000.0;
let (fst, snd) = data.split_at_mut(48_000 * 5);
for (fst, snd) in fst.iter_mut().zip(snd.iter_mut()) {
let val = f32::sin(accumulator);
*fst = val as f64;
*snd = val as f64;
accumulator += step;
}
let mut ebu = EbuR128::new(2, 48_000, Mode::all() & !Mode::HISTOGRAM).unwrap();
ebu.add_frames_planar_f64(&[fst, snd]).unwrap();
assert_float_eq!(
ebu.loudness_global().unwrap(),
-0.6826039914165554,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_momentary().unwrap(),
-0.6813325598268921,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_shortterm().unwrap(),
-0.6827591715100236,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_window(1).unwrap(),
-0.8742956620008693,
abs <= 0.000001
);
assert_float_eq!(
ebu.loudness_range().unwrap(),
0.00006921150169403312,
abs <= 0.000001
);
assert_float_eq!(ebu.sample_peak(0).unwrap(), 1.0, abs <= 0.000001);
assert_float_eq!(ebu.sample_peak(1).unwrap(), 1.0, abs <= 0.000001);
assert_float_eq!(ebu.prev_sample_peak(0).unwrap(), 1.0, abs <= 0.000001);
assert_float_eq!(ebu.prev_sample_peak(1).unwrap(), 1.0, abs <= 0.000001);
assert_float_eq!(
ebu.true_peak(0).unwrap(),
1.0008491277694702,
abs <= 0.000001
);
assert_float_eq!(
ebu.true_peak(1).unwrap(),
1.0008491277694702,
abs <= 0.000001
);
assert_float_eq!(
ebu.prev_true_peak(0).unwrap(),
1.0008491277694702,
abs <= 0.000001
);
assert_float_eq!(
ebu.prev_true_peak(1).unwrap(),
1.0008491277694702,
abs <= 0.000001
);
assert_float_eq!(
ebu.relative_threshold().unwrap(),
-10.682603991416554,
abs <= 0.000001
);
}
#[test]
fn sine_stereo_f32_multiple() {
let mut data = vec![0.0f32; 48_000 * 5 * 2];
let mut accumulator = 0.0;
let step = 2.0 * std::f32::consts::PI * 440.0 / 48_000.0;
for out in data.chunks_exact_mut(2) {
let val = f32::sin(accumulator);
out[0] = val;
out[1] = val;
accumulator += step;
}
let mut ebu1 = EbuR128::new(2, 48_000, Mode::all()).unwrap();
ebu1.add_frames_f32(&data).unwrap();
let mut data = vec![0.0f32; 48_000 * 5 * 2];
let mut accumulator = 0.0;
let step = 2.0 * std::f32::consts::PI * 880.0 / 48_000.0;
for out in data.chunks_exact_mut(2) {
let val = f32::sin(accumulator);
out[0] = 0.5 * val;
out[1] = 0.5 * val;
accumulator += step;
}
let mut ebu2 = EbuR128::new(2, 48_000, Mode::all()).unwrap();
ebu2.add_frames_f32(&data).unwrap();
assert_float_eq!(
EbuR128::loudness_global_multiple([&ebu1, &ebu2].iter().copied()).unwrap(),
-2.603757953612454,
abs <= 0.000001
);
assert_float_eq!(
EbuR128::loudness_range_multiple([&ebu1, &ebu2].iter().copied()).unwrap(),
5.599999999999995,
abs <= 0.000001
);
}
#[test]
fn sine_stereo_f32_no_histogram_multiple() {
let mut data = vec![0.0f32; 48_000 * 5 * 2];
let mut accumulator = 0.0;
let step = 2.0 * std::f32::consts::PI * 440.0 / 48_000.0;
for out in data.chunks_exact_mut(2) {
let val = f32::sin(accumulator);
out[0] = val;
out[1] = val;
accumulator += step;
}
let mut ebu1 = EbuR128::new(2, 48_000, Mode::all() & !Mode::HISTOGRAM).unwrap();
ebu1.add_frames_f32(&data).unwrap();
let mut data = vec![0.0f32; 48_000 * 5 * 2];
let mut accumulator = 0.0;
let step = 2.0 * std::f32::consts::PI * 880.0 / 48_000.0;
for out in data.chunks_exact_mut(2) {
let val = f32::sin(accumulator);
out[0] = 0.5 * val;
out[1] = 0.5 * val;
accumulator += step;
}
let mut ebu2 = EbuR128::new(2, 48_000, Mode::all() & !Mode::HISTOGRAM).unwrap();
ebu2.add_frames_f32(&data).unwrap();
assert_float_eq!(
EbuR128::loudness_global_multiple([&ebu1, &ebu2].iter().copied()).unwrap(),
-2.6302830567858275,
abs <= 0.000001
);
assert_float_eq!(
EbuR128::loudness_range_multiple([&ebu1, &ebu2].iter().copied()).unwrap(),
5.571749801957784,
abs <= 0.000001
);
}
#[cfg(feature = "c-tests")]
fn compare_results(ebu: &EbuR128, ebu_c: &ebur128_c::EbuR128, channels: u32) {
assert_float_eq!(
ebu.loudness_global().unwrap(),
ebu_c.loudness_global().unwrap(),
ulps <= 2
);
assert_float_eq!(
ebu.loudness_momentary().unwrap(),
ebu_c.loudness_momentary().unwrap(),
ulps <= 2
);
assert_float_eq!(
ebu.loudness_shortterm().unwrap(),
ebu_c.loudness_shortterm().unwrap(),
ulps <= 2
);
assert_float_eq!(
ebu.loudness_window(1).unwrap(),
ebu_c.loudness_window(1).unwrap(),
ulps <= 2
);
assert_float_eq!(
ebu.loudness_range().unwrap(),
ebu_c.loudness_range().unwrap(),
ulps <= 2
);
for c in 0..channels {
assert_float_eq!(
ebu.sample_peak(c).unwrap(),
ebu_c.sample_peak(c).unwrap(),
ulps <= 2
);
assert_float_eq!(
ebu.prev_sample_peak(c).unwrap(),
ebu_c.prev_sample_peak(c).unwrap(),
ulps <= 2
);
assert_float_eq!(
ebu.true_peak(c).unwrap(),
ebu_c.true_peak(c).unwrap(),
ulps <= 2
);
assert_float_eq!(
ebu.prev_true_peak(c).unwrap(),
ebu_c.prev_true_peak(c).unwrap(),
ulps <= 2
);
}
assert_float_eq!(
ebu.relative_threshold().unwrap(),
ebu_c.relative_threshold().unwrap(),
ulps <= 2
);
}
#[cfg(feature = "c-tests")]
#[quickcheck]
fn compare_c_impl_i16(signal: Signal<i16>) {
let mut ebu = EbuR128::new(signal.channels, signal.rate, Mode::all()).unwrap();
ebu.add_frames_i16(&signal.data).unwrap();
let mut ebu_c =
ebur128_c::EbuR128::new(signal.channels, signal.rate, ebur128_c::Mode::all()).unwrap();
ebu_c.add_frames_i16(&signal.data).unwrap();
compare_results(&ebu, &ebu_c, signal.channels);
}
#[cfg(feature = "c-tests")]
#[quickcheck]
fn compare_c_impl_i32(signal: Signal<i32>) {
let mut ebu = EbuR128::new(signal.channels, signal.rate, Mode::all()).unwrap();
ebu.add_frames_i32(&signal.data).unwrap();
let mut ebu_c =
ebur128_c::EbuR128::new(signal.channels, signal.rate, ebur128_c::Mode::all()).unwrap();
ebu_c.add_frames_i32(&signal.data).unwrap();
compare_results(&ebu, &ebu_c, signal.channels);
}
#[cfg(feature = "c-tests")]
#[quickcheck]
fn compare_c_impl_f32(signal: Signal<f32>) {
let mut ebu = EbuR128::new(signal.channels, signal.rate, Mode::all()).unwrap();
ebu.add_frames_f32(&signal.data).unwrap();
let mut ebu_c =
ebur128_c::EbuR128::new(signal.channels, signal.rate, ebur128_c::Mode::all()).unwrap();
ebu_c.add_frames_f32(&signal.data).unwrap();
compare_results(&ebu, &ebu_c, signal.channels);
}
#[cfg(feature = "c-tests")]
#[quickcheck]
fn compare_c_impl_f64(signal: Signal<f64>) {
let mut ebu = EbuR128::new(signal.channels, signal.rate, Mode::all()).unwrap();
ebu.add_frames_f64(&signal.data).unwrap();
let mut ebu_c =
ebur128_c::EbuR128::new(signal.channels, signal.rate, ebur128_c::Mode::all()).unwrap();
ebu_c.add_frames_f64(&signal.data).unwrap();
compare_results(&ebu, &ebu_c, signal.channels);
}
#[cfg(feature = "c-tests")]
#[quickcheck]
fn compare_c_impl_i16_no_histogram(signal: Signal<i16>) {
let mut ebu =
EbuR128::new(signal.channels, signal.rate, Mode::all() & !Mode::HISTOGRAM).unwrap();
ebu.add_frames_i16(&signal.data).unwrap();
let mut ebu_c = ebur128_c::EbuR128::new(
signal.channels,
signal.rate,
ebur128_c::Mode::all() & !ebur128_c::Mode::HISTOGRAM,
)
.unwrap();
ebu_c.add_frames_i16(&signal.data).unwrap();
compare_results(&ebu, &ebu_c, signal.channels);
}
#[cfg(feature = "c-tests")]
#[quickcheck]
fn compare_c_impl_i32_no_histogram(signal: Signal<i32>) {
let mut ebu =
EbuR128::new(signal.channels, signal.rate, Mode::all() & !Mode::HISTOGRAM).unwrap();
ebu.add_frames_i32(&signal.data).unwrap();
let mut ebu_c = ebur128_c::EbuR128::new(
signal.channels,
signal.rate,
ebur128_c::Mode::all() & !ebur128_c::Mode::HISTOGRAM,
)
.unwrap();
ebu_c.add_frames_i32(&signal.data).unwrap();
compare_results(&ebu, &ebu_c, signal.channels);
}
#[cfg(feature = "c-tests")]
#[quickcheck]
fn compare_c_impl_f32_no_histogram(signal: Signal<f32>) {
let mut ebu =
EbuR128::new(signal.channels, signal.rate, Mode::all() & !Mode::HISTOGRAM).unwrap();
ebu.add_frames_f32(&signal.data).unwrap();
let mut ebu_c = ebur128_c::EbuR128::new(
signal.channels,
signal.rate,
ebur128_c::Mode::all() & !ebur128_c::Mode::HISTOGRAM,
)
.unwrap();
ebu_c.add_frames_f32(&signal.data).unwrap();
compare_results(&ebu, &ebu_c, signal.channels);
}
#[cfg(feature = "c-tests")]
#[quickcheck]
fn compare_c_impl_f64_no_histogram(signal: Signal<f64>) {
let mut ebu =
EbuR128::new(signal.channels, signal.rate, Mode::all() & !Mode::HISTOGRAM).unwrap();
ebu.add_frames_f64(&signal.data).unwrap();
let mut ebu_c = ebur128_c::EbuR128::new(
signal.channels,
signal.rate,
ebur128_c::Mode::all() & !ebur128_c::Mode::HISTOGRAM,
)
.unwrap();
ebu_c.add_frames_f64(&signal.data).unwrap();
compare_results(&ebu, &ebu_c, signal.channels);
}
}