#![forbid(unsafe_code)]
#![deny(unsafe_op_in_unsafe_fn)]
pub mod design;
pub mod files;
mod plan;
mod validate;
pub use plan::{FilterPlan, FirView, MimoFeed, Op, SubFeed, headroom_db, plan_responses};
use secs_types::chain::{MimoPaths, SignalChain};
pub const SAMPLE_RATE: u32 = 48_000;
pub const LFE_GAIN_DB: f64 = 10.0;
pub const LFE_LP_HZ: f64 = 120.0;
pub fn round_ties_even_i64(value: f64) -> i64 {
let floor = value.floor();
let fraction = value - floor;
let rounded = if fraction < 0.5 {
floor
} else if fraction > 0.5 {
floor + 1.0
} else if (floor as i64) & 1 == 0 {
floor
} else {
floor + 1.0
};
rounded as i64
}
pub fn is_negative_delay(milliseconds: f64) -> bool {
round_ties_even_i64(milliseconds * f64::from(SAMPLE_RATE) / 1000.0) < 0
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LfeChoice {
Omit,
Include {
gain_db: f64,
lp_hz: f64,
},
}
impl LfeChoice {
pub const DOLBY: Self = Self::Include {
gain_db: LFE_GAIN_DB,
lp_hz: LFE_LP_HZ,
};
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum GraphError {
#[error("non-ASCII export identifier: {0}")]
NonAscii(String),
#[error("non-finite export value: {0}")]
NonFinite(String),
#[error("invalid export graph: {0}")]
Invalid(String),
}
#[derive(Debug, Clone, PartialEq)]
pub struct SupportGroup {
pub output: usize,
pub fir: bool,
pub low_hz: f64,
pub members: Vec<usize>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ExportGraph {
chain: SignalChain,
mimo: Option<MimoPaths>,
plan: FilterPlan,
support_groups: Vec<SupportGroup>,
}
impl ExportGraph {
pub fn build(
chain: &SignalChain,
mimo: Option<&MimoPaths>,
lfe: LfeChoice,
) -> Result<Self, GraphError> {
validate::graph(chain, mimo, lfe)?;
let plan = plan::make(chain, mimo, lfe);
let support_groups = support_groups(&plan.outputs, mimo);
Ok(Self {
chain: chain.clone(),
mimo: mimo.cloned(),
plan,
support_groups,
})
}
pub fn chain(&self) -> &SignalChain {
&self.chain
}
pub fn mimo(&self) -> Option<&MimoPaths> {
self.mimo.as_ref()
}
pub fn plan(&self) -> &FilterPlan {
&self.plan
}
pub fn support_groups(&self) -> &[SupportGroup] {
&self.support_groups
}
}
fn support_groups(outputs: &[String], mimo: Option<&MimoPaths>) -> Vec<SupportGroup> {
let mut groups: Vec<SupportGroup> = Vec::new();
for (index, path) in mimo.map_or(&[][..], |mimo| &mimo.paths).iter().enumerate() {
let output = outputs
.iter()
.position(|role| *role == path.support)
.expect("validated support");
let fir = path.filter.is_fir();
if let Some(group) = groups.iter_mut().find(|group| {
group.output == output
&& group.fir == fir
&& group.low_hz.to_bits() == path.low_hz.to_bits()
}) {
group.members.push(index);
} else {
groups.push(SupportGroup {
output,
fir,
low_hz: path.low_hz,
members: vec![index],
});
}
}
groups
}