#![forbid(unsafe_code)]
pub mod ballistics;
pub mod batch;
pub mod correlation;
pub mod peak;
pub mod ppm;
pub mod vu;
use crate::frame::AudioFrame;
pub use ballistics::{
BallisticsConfig, BallisticsProcessor, OverloadDetector, PeakDetector, RmsWindow,
};
pub use batch::{BatchMeterConfig, BatchMeterProcessor, BatchMeterReading};
pub use correlation::{
CorrelationMeter, CorrelationVisualization, GonioPoint, Goniometer, GoniometerMode,
GoniometerVisualization,
};
pub use peak::{
ColorZone as PeakColorZone, DigitalPeakMeter, PeakVisualization, RmsLevelMeter,
RmsVisualization,
};
pub use ppm::{ColorZone as PpmColorZone, PpmMeter, PpmStandard, PpmVisualization};
pub use vu::{ColorZone as VuColorZone, VuMeter, VuVisualization};
#[derive(Clone, Debug)]
pub struct MeterBridgeConfig {
pub enable_vu: bool,
pub enable_ppm: bool,
pub ppm_standard: PpmStandard,
pub enable_peak: bool,
pub peak_hold_time: f64,
pub enable_rms: bool,
pub rms_integration_time: f64,
pub enable_correlation: bool,
pub correlation_integration_time: f64,
pub enable_goniometer: bool,
pub goniometer_max_points: usize,
pub enable_lufs: bool,
}
impl MeterBridgeConfig {
#[must_use]
pub fn minimal() -> Self {
Self {
enable_vu: false,
enable_ppm: false,
ppm_standard: PpmStandard::Ebu,
enable_peak: true,
peak_hold_time: 2.0,
enable_rms: false,
rms_integration_time: 0.3,
enable_correlation: false,
correlation_integration_time: 0.4,
enable_goniometer: false,
goniometer_max_points: 1000,
enable_lufs: false,
}
}
#[must_use]
pub fn standard() -> Self {
Self {
enable_vu: true,
enable_ppm: false,
ppm_standard: PpmStandard::Ebu,
enable_peak: true,
peak_hold_time: 2.0,
enable_rms: true,
rms_integration_time: 0.3,
enable_correlation: false,
correlation_integration_time: 0.4,
enable_goniometer: false,
goniometer_max_points: 1000,
enable_lufs: false,
}
}
#[must_use]
pub fn professional() -> Self {
Self {
enable_vu: true,
enable_ppm: true,
ppm_standard: PpmStandard::Ebu,
enable_peak: true,
peak_hold_time: 2.0,
enable_rms: true,
rms_integration_time: 0.3,
enable_correlation: true,
correlation_integration_time: 0.4,
enable_goniometer: true,
goniometer_max_points: 2000,
enable_lufs: true,
}
}
#[must_use]
pub fn broadcast() -> Self {
Self {
enable_vu: false,
enable_ppm: true,
ppm_standard: PpmStandard::Ebu,
enable_peak: true,
peak_hold_time: 1.0,
enable_rms: false,
rms_integration_time: 0.3,
enable_correlation: true,
correlation_integration_time: 0.4,
enable_goniometer: true,
goniometer_max_points: 1500,
enable_lufs: true,
}
}
}
impl Default for MeterBridgeConfig {
fn default() -> Self {
Self::standard()
}
}
pub struct MeterBridge {
config: MeterBridgeConfig,
vu_meter: Option<VuMeter>,
ppm_meter: Option<PpmMeter>,
peak_meter: Option<DigitalPeakMeter>,
rms_meter: Option<RmsLevelMeter>,
correlation_meter: Option<CorrelationMeter>,
goniometer: Option<Goniometer>,
lufs_meter: Option<crate::loudness::LoudnessMeter>,
#[allow(dead_code)]
sample_rate: f64,
#[allow(dead_code)]
channels: usize,
}
impl MeterBridge {
#[must_use]
pub fn new(config: MeterBridgeConfig, sample_rate: f64, channels: usize) -> Self {
let vu_meter = if config.enable_vu {
Some(VuMeter::new(sample_rate, channels))
} else {
None
};
let ppm_meter = if config.enable_ppm {
Some(PpmMeter::new(config.ppm_standard, sample_rate, channels))
} else {
None
};
let peak_meter = if config.enable_peak {
Some(DigitalPeakMeter::new(
sample_rate,
channels,
config.peak_hold_time,
))
} else {
None
};
let rms_meter = if config.enable_rms {
Some(RmsLevelMeter::new(
sample_rate,
channels,
config.rms_integration_time,
))
} else {
None
};
let correlation_meter = if config.enable_correlation && channels >= 2 {
Some(CorrelationMeter::new(
sample_rate,
config.correlation_integration_time,
))
} else {
None
};
let goniometer = if config.enable_goniometer && channels >= 2 {
Some(Goniometer::new(
sample_rate,
config.goniometer_max_points,
GoniometerMode::LR,
))
} else {
None
};
let lufs_meter = if config.enable_lufs {
Some(crate::loudness::LoudnessMeter::new(
crate::loudness::LoudnessStandard::EbuR128,
sample_rate,
channels,
))
} else {
None
};
Self {
config,
vu_meter,
ppm_meter,
peak_meter,
rms_meter,
correlation_meter,
goniometer,
lufs_meter,
sample_rate,
channels,
}
}
pub fn process(&mut self, frame: &AudioFrame) -> MeterReadings {
if let Some(ref mut meter) = self.vu_meter {
meter.process(frame);
}
if let Some(ref mut meter) = self.ppm_meter {
meter.process(frame);
}
if let Some(ref mut meter) = self.peak_meter {
meter.process(frame);
}
if let Some(ref mut meter) = self.rms_meter {
meter.process(frame);
}
if let Some(ref mut meter) = self.correlation_meter {
meter.process(frame);
}
if let Some(ref mut meter) = self.goniometer {
meter.process(frame);
}
if let Some(ref mut meter) = self.lufs_meter {
meter.measure(frame);
}
self.get_readings()
}
#[must_use]
pub fn get_readings(&self) -> MeterReadings {
let mut peak_dbfs = vec![f64::NEG_INFINITY; self.channels];
let mut rms_dbfs = vec![f64::NEG_INFINITY; self.channels];
let mut vu_dbvu = vec![f64::NEG_INFINITY; self.channels];
let mut ppm_db = vec![f64::NEG_INFINITY; self.channels];
if let Some(ref meter) = self.peak_meter {
for ch in 0..self.channels {
peak_dbfs[ch] = meter.peak_dbfs(ch);
}
}
if let Some(ref meter) = self.rms_meter {
for ch in 0..self.channels {
rms_dbfs[ch] = meter.rms_dbfs(ch);
}
}
if let Some(ref meter) = self.vu_meter {
for ch in 0..self.channels {
vu_dbvu[ch] = meter.vu_reading(ch);
}
}
if let Some(ref meter) = self.ppm_meter {
for ch in 0..self.channels {
ppm_db[ch] = meter.ppm_reading(ch);
}
}
let correlation = self
.correlation_meter
.as_ref()
.map_or(0.0, CorrelationMeter::correlation);
let lufs = self
.lufs_meter
.as_ref()
.map(|m| m.get_metrics().integrated_lufs);
MeterReadings {
peak_dbfs,
rms_dbfs,
vu_dbvu,
ppm_db,
correlation,
lufs,
has_overload: self.has_overload(),
has_phase_issues: self
.correlation_meter
.as_ref()
.map_or(false, CorrelationMeter::has_phase_issues),
}
}
#[must_use]
pub fn has_overload(&self) -> bool {
if let Some(ref meter) = self.peak_meter {
(0..self.channels).any(|ch| meter.is_overload(ch))
} else if let Some(ref meter) = self.vu_meter {
(0..self.channels).any(|ch| meter.is_overload(ch))
} else if let Some(ref meter) = self.ppm_meter {
(0..self.channels).any(|ch| meter.is_overload(ch))
} else {
false
}
}
pub fn reset(&mut self) {
if let Some(ref mut meter) = self.vu_meter {
meter.reset();
}
if let Some(ref mut meter) = self.ppm_meter {
meter.reset();
}
if let Some(ref mut meter) = self.peak_meter {
meter.reset();
}
if let Some(ref mut meter) = self.rms_meter {
meter.reset();
}
if let Some(ref mut meter) = self.correlation_meter {
meter.reset();
}
if let Some(ref mut meter) = self.goniometer {
meter.clear();
}
if let Some(ref mut meter) = self.lufs_meter {
meter.reset();
}
}
pub fn reset_peaks(&mut self) {
if let Some(ref mut meter) = self.vu_meter {
meter.reset_peaks();
}
if let Some(ref mut meter) = self.ppm_meter {
meter.reset_peaks();
}
if let Some(ref mut meter) = self.peak_meter {
meter.reset_peak_hold();
}
}
#[must_use]
pub fn vu_meter(&self) -> Option<&VuMeter> {
self.vu_meter.as_ref()
}
#[must_use]
pub fn ppm_meter(&self) -> Option<&PpmMeter> {
self.ppm_meter.as_ref()
}
#[must_use]
pub fn peak_meter(&self) -> Option<&DigitalPeakMeter> {
self.peak_meter.as_ref()
}
#[must_use]
pub fn rms_meter(&self) -> Option<&RmsLevelMeter> {
self.rms_meter.as_ref()
}
#[must_use]
pub fn correlation_meter(&self) -> Option<&CorrelationMeter> {
self.correlation_meter.as_ref()
}
#[must_use]
pub fn goniometer(&self) -> Option<&Goniometer> {
self.goniometer.as_ref()
}
#[must_use]
pub fn lufs_meter(&self) -> Option<&crate::loudness::LoudnessMeter> {
self.lufs_meter.as_ref()
}
#[must_use]
pub fn config(&self) -> &MeterBridgeConfig {
&self.config
}
}
#[derive(Clone, Debug)]
pub struct MeterReadings {
pub peak_dbfs: Vec<f64>,
pub rms_dbfs: Vec<f64>,
pub vu_dbvu: Vec<f64>,
pub ppm_db: Vec<f64>,
pub correlation: f64,
pub lufs: Option<f64>,
pub has_overload: bool,
pub has_phase_issues: bool,
}
impl MeterReadings {
#[must_use]
pub fn stereo_peak_dbfs(&self) -> f64 {
self.peak_dbfs
.iter()
.copied()
.fold(f64::NEG_INFINITY, f64::max)
}
#[must_use]
pub fn stereo_rms_dbfs(&self) -> f64 {
if self.rms_dbfs.len() >= 2 {
let left = self.rms_dbfs[0];
let right = self.rms_dbfs[1];
if left.is_finite() && right.is_finite() {
let left_lin = ballistics::db_to_linear(left);
let right_lin = ballistics::db_to_linear(right);
ballistics::linear_to_db((left_lin + right_lin) / 2.0)
} else if left.is_finite() {
left
} else {
right
}
} else if let Some(&first) = self.rms_dbfs.first() {
first
} else {
f64::NEG_INFINITY
}
}
#[must_use]
pub fn is_broadcast_safe(&self) -> bool {
!self.has_overload
&& !self.has_phase_issues
&& self
.lufs
.map_or(true, |l| l.is_finite() && l > -40.0 && l < -10.0)
}
}