selene-core 0.9.0-rc.1

Backend for selene-server
Documentation
use std::f64;

use ebur128::{EbuR128, Mode};
use selene_common::loudnorm::Loudnorm;
use serde::{Deserialize, Serialize};
use symphonia::core::audio::{Audio, GenericAudioBufferRef};
use thiserror::Error;

use crate::{
    config::common_config,
    library::track::Track,
    transcoding::decoder::{Decoder, DecodingError},
};

#[derive(Debug, Error)]
pub enum LoudnormAnalysisError {
    #[error("{0}")]
    Ebur128(#[from] ebur128::Error),

    #[error("Decoding Error: {0}")]
    Decoding(#[from] DecodingError),
}

/// [`LoudnormAnalysis`] represents the measured values from the first-pass of EBU R 128 loudnorm
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub struct LoudnormAnalysis {
    pub(crate) accurate_true_peak: bool,

    pub(crate) i: f64,
    pub(crate) tp: f64,
}

impl From<LoudnormAnalysis> for Loudnorm {
    fn from(value: LoudnormAnalysis) -> Self {
        Loudnorm {
            i: value.i,
            tp: value.tp,
        }
    }
}

impl LoudnormAnalysis {
    #[must_use]
    pub fn accurate_true_peak(&self) -> bool {
        self.accurate_true_peak
    }

    #[must_use]
    pub fn measured_i(&self) -> f64 {
        self.i
    }

    #[must_use]
    pub fn measured_tp(&self) -> f64 {
        self.tp
    }

    #[must_use]
    pub fn calculated_gain_db(&self) -> f64 {
        let config = common_config().loudnorm;
        (config.target_i - self.i - config.target_offset).min(config.target_tp - self.tp)
    }

    #[must_use]
    pub fn calculated_gain(&self) -> f64 {
        10f64.powf(self.calculated_gain_db() / 20.0)
    }

    #[must_use]
    pub fn calculated_replay_gain_peak(&self) -> f64 {
        10.0_f64.powf(self.tp / 20.0)
    }

    /// Uses [`symphonia`] and [`ebur128`] to analyze loudnorm for a given file
    pub fn from_track(
        track: &Track,
        accurate_true_peak: bool,
    ) -> Result<Self, LoudnormAnalysisError> {
        let peak_mode = if accurate_true_peak {
            Mode::TRUE_PEAK
        } else {
            Mode::SAMPLE_PEAK
        };

        let mut analyzer = EbuR128::new(
            track.container.stream.codec_params.channels as u32,
            track.container.stream.codec_params.sample_rate,
            Mode::I | peak_mode,
        )?;

        let mut raw_decoder = Decoder::from_track(track, 512 * 1024)?;

        while let Some(packet) = raw_decoder.decode_next_packet()? {
            match packet {
                GenericAudioBufferRef::S16(buf) => {
                    let mut frames = vec![0; buf.samples_interleaved()];
                    buf.copy_to_slice_interleaved(&mut frames);
                    analyzer.add_frames_i16(&frames)?;
                }
                GenericAudioBufferRef::S32(buf) => {
                    let mut frames = vec![0; buf.samples_interleaved()];
                    buf.copy_to_slice_interleaved(&mut frames);
                    analyzer.add_frames_i32(&frames)?;
                }
                GenericAudioBufferRef::F64(buf) => {
                    let mut frames = vec![0.0; buf.samples_interleaved()];
                    buf.copy_to_slice_interleaved(&mut frames);
                    analyzer.add_frames_f64(&frames)?;
                }
                buf => {
                    let mut frames = vec![0.0; buf.samples_interleaved()];
                    buf.copy_to_slice_interleaved(&mut frames);
                    analyzer.add_frames_f32(&frames)?;
                }
            }
        }

        let i = analyzer.loudness_global()?;
        let tp = (0..track.container.stream.codec_params.channels as u32).try_fold(
            f64::NEG_INFINITY,
            |max, c| {
                let peak = if accurate_true_peak {
                    analyzer.true_peak(c)
                } else {
                    analyzer.sample_peak(c)
                };
                peak.map(|tp| f64::max(max, tp))
            },
        )?;

        Ok(LoudnormAnalysis {
            accurate_true_peak,
            i,
            tp,
        })
    }
}