use std::{
cmp::Ordering,
fmt::{self, Display, Formatter},
};
use derive_more::Constructor;
use getset::{Getters, MutGetters, Setters};
#[derive(Clone, Constructor, Debug, Default, Getters, Setters, PartialEq)]
pub struct Interval {
#[getset(get = "pub")]
xmin: f64,
#[getset(get = "pub")]
xmax: f64,
#[getset(get = "pub", set = "pub")]
text: String,
}
impl Interval {
#[must_use]
pub fn get_duration(&self) -> f64 {
self.xmax - self.xmin
}
#[must_use]
pub fn get_midpoint(&self) -> f64 {
(self.xmin + self.xmax) / 2.0
}
pub fn set_xmin(&mut self, xmin: f64) {
self.xmin = xmin;
}
pub fn set_xmax(&mut self, xmax: f64) {
self.xmax = xmax;
}
}
impl Display for Interval {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
writeln!(f, "Interval")?;
writeln!(f, " xmin = {}", self.xmin)?;
writeln!(f, " xmax = {}", self.xmax)?;
writeln!(f, " text = \"{}\"", self.text)?;
Ok(())
}
}
#[derive(Clone, Constructor, Debug, Default, Getters, MutGetters, Setters, PartialEq)]
pub struct Tier {
#[getset(get = "pub", set = "pub")]
name: String,
#[getset(get = "pub")]
xmin: f64,
#[getset(get = "pub")]
xmax: f64,
#[getset(get = "pub", get_mut = "pub")]
intervals: Vec<Interval>,
}
impl Tier {
pub fn set_xmin<T: Into<Option<bool>>>(&mut self, xmin: f64, warn: T) {
if warn.into().unwrap_or_default() {
let min_point = self
.intervals
.iter()
.filter_map(|intervals| {
intervals
.xmin
.partial_cmp(&f64::INFINITY)
.map(|_| intervals.xmin)
})
.min_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Greater));
if min_point.is_some_and(|min| xmin > min) {
eprintln!("Warning: Tier `{}` has a minimum point of {} but the TextGrid has an xmin of {}", self.name, min_point.unwrap_or_default(), xmin);
}
}
self.xmin = xmin;
}
pub fn set_xmax<W: Into<Option<bool>>>(&mut self, xmax: f64, warn: W) {
if warn.into().unwrap_or_default() {
let max_point = self
.intervals
.iter()
.filter_map(|interval| {
interval
.xmax
.partial_cmp(&f64::INFINITY)
.map(|_| interval.xmax)
})
.max_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Less));
if max_point.is_some_and(|max| xmax < max) {
eprintln!("Warning: Tier `{}` has a minimum point of {} but the TextGrid has an xmax of {}", self.name, max_point.unwrap_or_default(), xmax);
}
}
self.xmax = xmax;
}
#[must_use]
pub fn get_size(&self) -> usize {
self.intervals.len()
}
pub fn push_interval<W: Into<Option<bool>>>(&mut self, interval: Interval, warn: W) {
if warn.into().unwrap_or_default() && interval.xmin < self.xmin {
eprintln!(
"Warning: Tier `{}` has a minimum point of {} but the TextGrid has an xmin of {}",
self.name, interval.xmin, self.xmin
);
}
self.intervals.push(interval);
self.reorder();
}
pub fn push_intervals<W: Into<Option<bool>> + Copy>(
&mut self,
intervals: Vec<Interval>,
warn: W,
) {
for interval in &intervals {
if warn.into().unwrap_or_default() {
if interval.xmin < self.xmin {
eprintln!(
"Warning: Tier `{}` has a minimum point of {} but the TextGrid has an xmin of {}",
self.name, interval.xmin, self.xmin
);
}
if interval.xmax > self.xmax {
eprintln!(
"Warning: Tier `{}` has a maximum point of {} but the TextGrid has an xmax of {}",
self.name, interval.xmax, self.xmax
);
}
}
}
self.intervals.extend(intervals);
self.reorder();
}
pub fn set_intervals<W: Into<Option<bool>>>(&mut self, intervals: Vec<Interval>, warn: W) {
if warn.into().unwrap_or_default() {
for interval in &intervals {
if interval.xmin < self.xmin {
eprintln!(
"Warning: Tier `{}` has a minimum point of {} but the TextGrid has an xmin of {}",
self.name, interval.xmin, self.xmin
);
}
if interval.xmax > self.xmax {
eprintln!(
"Warning: Tier `{}` has a maximum point of {} but the TextGrid has an xmax of {}",
self.name, interval.xmax, self.xmax
);
}
}
}
self.intervals = intervals;
}
fn reorder(&mut self) {
self.intervals
.sort_by(|a, b| a.xmin.partial_cmp(&b.xmin).unwrap_or(Ordering::Equal));
}
#[must_use]
pub fn check_overlaps(&self) -> Option<Vec<(u64, u64)>> {
let mut overlaps: Vec<(u64, u64)> = Vec::new();
for (i, window) in self.intervals.windows(2).enumerate() {
let interval = &window[0];
let next_interval = &window[1];
#[allow(clippy::float_cmp)]
if interval.xmax != next_interval.xmin {
overlaps.push((i as u64, (i + 1) as u64));
}
}
if overlaps.is_empty() {
None
} else {
Some(overlaps)
}
}
pub fn fix_boundaries<P: Into<Option<bool>> + Copy>(&mut self, prefer_first: P) {
if self.intervals.len() < 2 {
return;
}
self.reorder();
if prefer_first.into().unwrap_or(true) {
for i in (1..self.intervals.len()).rev() {
let prev_interval = self.intervals[i - 1].clone();
let interval = &mut self.intervals[i];
#[allow(clippy::float_cmp)]
if interval.xmin != prev_interval.xmax {
interval.xmin = prev_interval.xmax;
}
}
} else {
for i in 0..self.intervals.len() - 1 {
let next_interval = self.intervals[i + 1].clone();
let interval = &mut self.intervals[i];
#[allow(clippy::float_cmp)]
if interval.xmax != next_interval.xmin {
interval.xmax = next_interval.xmin;
}
}
}
}
#[allow(clippy::float_cmp)]
pub fn fill_gaps(&mut self, text: &str) {
if self.intervals.len() < 2 {
return;
}
self.reorder();
let first_xmin = self.intervals.first().unwrap().xmin;
if first_xmin != self.xmin {
let new_interval = Interval::new(self.xmin, first_xmin, text.to_string());
self.intervals.insert(0, new_interval);
}
let last_xmax = self.intervals.last().unwrap().xmax;
if last_xmax != self.xmax {
let new_interval = Interval::new(last_xmax, self.xmax, text.to_string());
self.intervals.push(new_interval);
}
for (index, window) in self.intervals.clone().windows(2).enumerate() {
let interval = &window[0];
let next_interval = &window[1];
#[allow(clippy::float_cmp)]
if interval.xmax != next_interval.xmin {
let new_interval =
Interval::new(interval.xmax, next_interval.xmin, text.to_string());
self.intervals.insert(index + 1, new_interval);
}
}
}
}
impl Display for Tier {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(
f,
"IntervalTier {}:
xmin: {}
xmax: {}
interval count: {}",
self.name,
self.xmin,
self.xmax,
self.intervals.len()
)
}
}
#[cfg(test)]
#[allow(clippy::float_cmp)]
mod test_interval_tier {
use crate::interval::Interval;
#[test]
fn get_duration() {
let interval = Interval::new(0.0, 2.3, "test".to_string());
assert_eq!(interval.get_duration(), 2.3);
}
#[test]
fn get_midpoint() {
let interval = Interval::new(0.0, 2.3, "test".to_string());
assert_eq!(interval.get_midpoint(), 1.15);
}
#[test]
fn set_xmin() {
let mut interval = Interval::new(0.0, 2.3, "test".to_string());
interval.set_xmin(1.0);
assert_eq!(interval.xmin, 1.0);
}
#[test]
fn set_xmax() {
let mut interval = Interval::new(0.0, 2.3, "test".to_string());
interval.set_xmax(1.0);
assert_eq!(interval.xmax, 1.0);
}
#[test]
fn to_string() {
let interval = Interval::new(0.0, 2.3, "test".to_string());
assert_eq!(
interval.to_string(),
"Interval\n xmin = 0\n xmax = 2.3\n text = \"test\"\n"
);
}
}
#[cfg(test)]
#[allow(clippy::float_cmp)]
mod test_tier {
use crate::interval::{Interval, Tier};
#[test]
fn set_xmin() {
let mut tier = Tier::new("test".to_string(), 0.0, 2.3, Vec::new());
tier.set_xmin(1.0, Some(true));
assert_eq!(tier.xmin, 1.0);
}
#[test]
fn set_xmax() {
let mut tier = Tier::new("test".to_string(), 0.0, 2.3, Vec::new());
tier.set_xmax(1.0, Some(true));
assert_eq!(tier.xmax, 1.0);
}
#[test]
fn get_size() {
let tier = Tier::new("test".to_string(), 0.0, 2.3, Vec::new());
assert_eq!(tier.get_size(), 0);
}
#[test]
fn push_interval() {
let mut tier = Tier::new("test".to_string(), 0.0, 2.3, Vec::new());
tier.push_interval(Interval::new(0.0, 1.0, "test".to_string()), Some(true));
assert_eq!(tier.intervals.len(), 1);
}
#[test]
fn push_intervals() {
let mut tier = Tier::new("test".to_string(), 0.0, 2.3, Vec::new());
tier.push_intervals(
vec![
Interval::new(0.0, 1.0, "test".to_string()),
Interval::new(1.0, 2.0, "test".to_string()),
],
Some(true),
);
assert_eq!(tier.intervals.len(), 2);
}
#[test]
fn set_intervals() {
let mut tier = Tier::new("test".to_string(), 0.0, 2.3, Vec::new());
tier.set_intervals(
vec![
Interval::new(0.0, 1.0, "test".to_string()),
Interval::new(1.0, 2.0, "test".to_string()),
],
Some(true),
);
assert_eq!(tier.intervals.len(), 2);
}
#[test]
#[allow(clippy::float_cmp)]
fn reorder() {
let mut tier = Tier::new("test".to_string(), 0.0, 2.3, Vec::new());
tier.push_intervals(
vec![
Interval::new(1.0, 2.0, "test".to_string()),
Interval::new(0.0, 1.0, "test".to_string()),
],
Some(true),
);
tier.reorder();
assert_eq!(tier.intervals[0].xmin, 0.0);
assert_eq!(tier.intervals[1].xmin, 1.0);
}
mod check_overlaps {
use crate::{
interval::{Interval, Tier as IntervalTier},
textgrid::{TextGrid, Tier},
};
#[test]
fn no_overlap() {
let mut textgrid = TextGrid::new(0.0, 2.3, Vec::new(), "test".to_string());
textgrid.push_tier(
Tier::IntervalTier(IntervalTier::new(
"John".to_string(),
0.0,
2.3,
vec![
Interval::new(0.0, 1.5, "daisy bell".to_string()),
Interval::new(1.5, 2.3, "daisy bell".to_string()),
],
)),
false,
);
let overlaps = textgrid.check_overlaps();
assert!(overlaps.is_none());
}
#[test]
fn overlap() {
let mut textgrid = TextGrid::new(0.0, 2.3, Vec::new(), "test".to_string());
textgrid.push_tier(
Tier::IntervalTier(IntervalTier::new(
"John".to_string(),
0.0,
2.3,
vec![
Interval::new(0.0, 1.5, "daisy bell".to_string()),
Interval::new(1.0, 2.3, "daisy bell".to_string()),
],
)),
false,
);
let overlaps = textgrid.check_overlaps().unwrap();
assert_eq!(overlaps.len(), 1);
assert_eq!(overlaps[0].0, "John");
assert_eq!(overlaps[0].1, (0, 1));
}
}
#[allow(clippy::float_cmp)]
mod fix_boundaries {
use crate::interval::{Interval, Tier};
#[test]
fn prefer_first() {
let mut tier = Tier::new("test".to_string(), 0.0, 2.3, Vec::new());
tier.push_intervals(
vec![
Interval::new(0.0, 1.2, "daisy".to_string()),
Interval::new(1.0, 1.75, "bell".to_string()),
Interval::new(1.5, 2.5, "answer".to_string()),
Interval::new(2.0, 5.0, "do".to_string()),
],
false,
);
tier.fix_boundaries(true);
assert_eq!(tier.intervals()[0].xmin(), &0.0);
assert_eq!(tier.intervals()[0].xmax(), &1.2);
assert_eq!(tier.intervals()[1].xmin(), &1.2);
assert_eq!(tier.intervals()[1].xmax(), &1.75);
assert_eq!(tier.intervals()[2].xmin(), &1.75);
assert_eq!(tier.intervals()[2].xmax(), &2.5);
assert_eq!(tier.intervals()[3].xmin(), &2.5);
assert_eq!(tier.intervals()[3].xmax(), &5.0);
}
#[test]
fn prefer_last() {
let mut tier = Tier::new("test".to_string(), 0.0, 2.3, Vec::new());
tier.push_intervals(
vec![
Interval::new(0.0, 1.2, "daisy".to_string()),
Interval::new(1.0, 1.75, "bell".to_string()),
Interval::new(1.5, 2.5, "answer".to_string()),
Interval::new(2.0, 5.0, "do".to_string()),
],
false,
);
tier.fix_boundaries(false);
assert_eq!(tier.intervals()[0].xmin(), &0.0);
assert_eq!(tier.intervals()[0].xmax(), &1.0);
assert_eq!(tier.intervals()[1].xmin(), &1.0);
assert_eq!(tier.intervals()[1].xmax(), &1.5);
assert_eq!(tier.intervals()[2].xmin(), &1.5);
assert_eq!(tier.intervals()[2].xmax(), &2.0);
assert_eq!(tier.intervals()[3].xmin(), &2.0);
assert_eq!(tier.intervals()[3].xmax(), &5.0);
}
}
#[test]
#[allow(clippy::float_cmp)]
fn fill_gaps() {
let mut tier = Tier::new("test".to_string(), 0.0, 2.3, Vec::new());
tier.push_intervals(
vec![
Interval::new(0.0, 1.2, "daisy".to_string()),
Interval::new(1.5, 2.3, "bell".to_string()),
],
false,
);
tier.fill_gaps("gap");
assert_eq!(tier.intervals()[1].text(), "gap");
assert_eq!(tier.intervals()[1].xmin(), &1.2);
assert_eq!(tier.intervals()[1].xmax(), &1.5);
}
#[test]
fn to_string() {
let tier = Tier::new("test".to_string(), 0.0, 2.3, Vec::new());
assert_eq!(
tier.to_string(),
"IntervalTier test:
xmin: 0
xmax: 2.3
interval count: 0"
);
}
}