1use std::{
2 cmp::Ordering,
3 fmt::{self, Display, Formatter},
4};
5
6use derive_more::Constructor;
7use getset::{Getters, MutGetters, Setters};
8
9#[derive(Clone, Constructor, Debug, Default, Getters, Setters)]
11pub struct Interval {
12 #[getset(get = "pub")]
13 xmin: f64,
14 #[getset(get = "pub")]
15 xmax: f64,
16 #[getset(get = "pub", set = "pub")]
17 text: String,
18}
19
20impl Interval {
21 #[must_use]
23 pub fn get_duration(&self) -> f64 {
24 self.xmax - self.xmin
25 }
26
27 #[must_use]
29 pub fn get_midpoint(&self) -> f64 {
30 (self.xmin + self.xmax) / 2.0
31 }
32
33 pub fn set_xmin(&mut self, xmin: f64) {
39 self.xmin = xmin;
40 }
41
42 pub fn set_xmax(&mut self, xmax: f64) {
48 self.xmax = xmax;
49 }
50}
51
52impl Display for Interval {
53 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
54 writeln!(f, "Interval")?;
55 writeln!(f, " xmin = {}", self.xmin)?;
56 writeln!(f, " xmax = {}", self.xmax)?;
57 writeln!(f, " text = \"{}\"", self.text)?;
58 Ok(())
59 }
60}
61
62#[derive(Clone, Constructor, Debug, Default, Getters, MutGetters, Setters)]
64pub struct Tier {
65 #[getset(get = "pub", set = "pub")]
66 name: String,
67 #[getset(get = "pub")]
68 xmin: f64,
69 #[getset(get = "pub")]
70 xmax: f64,
71 #[getset(get = "pub", get_mut = "pub")]
72 intervals: Vec<Interval>,
73}
74
75impl Tier {
76 pub fn set_xmin<T: Into<Option<bool>>>(&mut self, xmin: f64, warn: T) {
83 if warn.into().unwrap_or_default() {
84 let min_point = self
85 .intervals
86 .iter()
87 .filter_map(|intervals| {
88 intervals
89 .xmin
90 .partial_cmp(&f64::INFINITY)
91 .map(|_| intervals.xmin)
92 })
93 .min_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Greater)); if min_point.is_some_and(|min| xmin > min) {
96 eprintln!("Warning: Tier `{}` has a minimum point of {} but the TextGrid has an xmin of {}", self.name, min_point.unwrap_or_default(), xmin);
97 }
98 }
99
100 self.xmin = xmin;
101 }
102
103 pub fn set_xmax<W: Into<Option<bool>>>(&mut self, xmax: f64, warn: W) {
110 if warn.into().unwrap_or_default() {
111 let max_point = self
112 .intervals
113 .iter()
114 .filter_map(|interval| {
115 interval
116 .xmax
117 .partial_cmp(&f64::INFINITY)
118 .map(|_| interval.xmax)
119 })
120 .max_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Less)); if max_point.is_some_and(|max| xmax < max) {
123 eprintln!("Warning: Tier `{}` has a minimum point of {} but the TextGrid has an xmax of {}", self.name, max_point.unwrap_or_default(), xmax);
124 }
125 }
126
127 self.xmax = xmax;
128 }
129
130 #[must_use]
132 pub fn get_size(&self) -> usize {
133 self.intervals.len()
134 }
135
136 pub fn push_interval<W: Into<Option<bool>>>(&mut self, interval: Interval, warn: W) {
144 if warn.into().unwrap_or_default() && interval.xmin < self.xmin {
145 eprintln!(
146 "Warning: Tier `{}` has a minimum point of {} but the TextGrid has an xmin of {}",
147 self.name, interval.xmin, self.xmin
148 );
149 }
150 self.intervals.push(interval);
151
152 self.reorder();
153 }
154
155 pub fn push_intervals<W: Into<Option<bool>> + Copy>(
163 &mut self,
164 intervals: Vec<Interval>,
165 warn: W,
166 ) {
167 for interval in &intervals {
168 if warn.into().unwrap_or_default() {
169 if interval.xmin < self.xmin {
170 eprintln!(
171 "Warning: Tier `{}` has a minimum point of {} but the TextGrid has an xmin of {}",
172 self.name, interval.xmin, self.xmin
173 );
174 }
175 if interval.xmax > self.xmax {
176 eprintln!(
177 "Warning: Tier `{}` has a maximum point of {} but the TextGrid has an xmax of {}",
178 self.name, interval.xmax, self.xmax
179 );
180 }
181 }
182 }
183
184 self.intervals.extend(intervals);
185
186 self.reorder();
187 }
188
189 pub fn set_intervals<W: Into<Option<bool>>>(&mut self, intervals: Vec<Interval>, warn: W) {
196 if warn.into().unwrap_or_default() {
197 for interval in &intervals {
198 if interval.xmin < self.xmin {
199 eprintln!(
200 "Warning: Tier `{}` has a minimum point of {} but the TextGrid has an xmin of {}",
201 self.name, interval.xmin, self.xmin
202 );
203 }
204 if interval.xmax > self.xmax {
205 eprintln!(
206 "Warning: Tier `{}` has a maximum point of {} but the TextGrid has an xmax of {}",
207 self.name, interval.xmax, self.xmax
208 );
209 }
210 }
211 }
212
213 self.intervals = intervals;
214 }
215
216 fn reorder(&mut self) {
218 self.intervals
219 .sort_by(|a, b| a.xmin.partial_cmp(&b.xmin).unwrap_or(Ordering::Equal));
220 }
221
222 #[must_use]
229 pub fn check_overlaps(&self) -> Option<Vec<(u64, u64)>> {
230 let mut overlaps: Vec<(u64, u64)> = Vec::new();
231
232 for (i, window) in self.intervals.windows(2).enumerate() {
234 let interval = &window[0];
235 let next_interval = &window[1];
236
237 #[allow(clippy::float_cmp)]
238 if interval.xmax != next_interval.xmin {
239 overlaps.push((i as u64, (i + 1) as u64));
240 }
241 }
242
243 if overlaps.is_empty() {
244 None
245 } else {
246 Some(overlaps)
247 }
248 }
249
250 pub fn fix_boundaries<P: Into<Option<bool>> + Copy>(&mut self, prefer_first: P) {
261 if self.intervals.len() < 2 {
262 return;
263 }
264
265 self.reorder();
266
267 if prefer_first.into().unwrap_or(true) {
272 for i in (1..self.intervals.len()).rev() {
275 let prev_interval = self.intervals[i - 1].clone();
276 let interval = &mut self.intervals[i];
277
278 #[allow(clippy::float_cmp)]
279 if interval.xmin != prev_interval.xmax {
280 interval.xmin = prev_interval.xmax;
281 }
282 }
283 } else {
284 for i in 0..self.intervals.len() - 1 {
285 let next_interval = self.intervals[i + 1].clone();
286 let interval = &mut self.intervals[i];
287
288 #[allow(clippy::float_cmp)]
289 if interval.xmax != next_interval.xmin {
290 interval.xmax = next_interval.xmin;
291 }
292 }
293 }
294 }
295
296 #[allow(clippy::float_cmp)]
307 pub fn fill_gaps(&mut self, text: &str) {
308 if self.intervals.len() < 2 {
309 return;
310 }
311
312 self.reorder();
313
314 let first_xmin = self.intervals.first().unwrap().xmin;
315 if first_xmin != self.xmin {
316 let new_interval = Interval::new(self.xmin, first_xmin, text.to_string());
317 self.intervals.insert(0, new_interval);
318 }
319
320 let last_xmax = self.intervals.last().unwrap().xmax;
321 if last_xmax != self.xmax {
322 let new_interval = Interval::new(last_xmax, self.xmax, text.to_string());
323 self.intervals.push(new_interval);
324 }
325
326 for (index, window) in self.intervals.clone().windows(2).enumerate() {
327 let interval = &window[0];
328 let next_interval = &window[1];
329
330 #[allow(clippy::float_cmp)]
331 if interval.xmax != next_interval.xmin {
332 let new_interval =
333 Interval::new(interval.xmax, next_interval.xmin, text.to_string());
334 self.intervals.insert(index + 1, new_interval);
335 }
336 }
337 }
338}
339
340impl Display for Tier {
341 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
342 write!(
343 f,
344 "IntervalTier {}:
345 xmin: {}
346 xmax: {}
347 interval count: {}",
348 self.name,
349 self.xmin,
350 self.xmax,
351 self.intervals.len()
352 )
353 }
354}
355
356#[cfg(test)]
357#[allow(clippy::float_cmp)]
358mod test_interval_tier {
359 use crate::interval::Interval;
360
361 #[test]
362 fn get_duration() {
363 let interval = Interval::new(0.0, 2.3, "test".to_string());
364
365 assert_eq!(interval.get_duration(), 2.3);
366 }
367
368 #[test]
369 fn get_midpoint() {
370 let interval = Interval::new(0.0, 2.3, "test".to_string());
371
372 assert_eq!(interval.get_midpoint(), 1.15);
373 }
374
375 #[test]
376 fn set_xmin() {
377 let mut interval = Interval::new(0.0, 2.3, "test".to_string());
378
379 interval.set_xmin(1.0);
380
381 assert_eq!(interval.xmin, 1.0);
382 }
383
384 #[test]
385 fn set_xmax() {
386 let mut interval = Interval::new(0.0, 2.3, "test".to_string());
387
388 interval.set_xmax(1.0);
389
390 assert_eq!(interval.xmax, 1.0);
391 }
392
393 #[test]
394 fn to_string() {
395 let interval = Interval::new(0.0, 2.3, "test".to_string());
396
397 assert_eq!(
398 interval.to_string(),
399 "Interval\n xmin = 0\n xmax = 2.3\n text = \"test\"\n"
400 );
401 }
402}
403
404#[cfg(test)]
405#[allow(clippy::float_cmp)]
406mod test_tier {
407 use crate::interval::{Interval, Tier};
408
409 #[test]
410 fn set_xmin() {
411 let mut tier = Tier::new("test".to_string(), 0.0, 2.3, Vec::new());
412
413 tier.set_xmin(1.0, Some(true));
414
415 assert_eq!(tier.xmin, 1.0);
416 }
417
418 #[test]
419 fn set_xmax() {
420 let mut tier = Tier::new("test".to_string(), 0.0, 2.3, Vec::new());
421
422 tier.set_xmax(1.0, Some(true));
423
424 assert_eq!(tier.xmax, 1.0);
425 }
426
427 #[test]
428 fn get_size() {
429 let tier = Tier::new("test".to_string(), 0.0, 2.3, Vec::new());
430
431 assert_eq!(tier.get_size(), 0);
432 }
433
434 #[test]
435 fn push_interval() {
436 let mut tier = Tier::new("test".to_string(), 0.0, 2.3, Vec::new());
437
438 tier.push_interval(Interval::new(0.0, 1.0, "test".to_string()), Some(true));
439
440 assert_eq!(tier.intervals.len(), 1);
441 }
442
443 #[test]
444 fn push_intervals() {
445 let mut tier = Tier::new("test".to_string(), 0.0, 2.3, Vec::new());
446
447 tier.push_intervals(
448 vec![
449 Interval::new(0.0, 1.0, "test".to_string()),
450 Interval::new(1.0, 2.0, "test".to_string()),
451 ],
452 Some(true),
453 );
454
455 assert_eq!(tier.intervals.len(), 2);
456 }
457
458 #[test]
459 fn set_intervals() {
460 let mut tier = Tier::new("test".to_string(), 0.0, 2.3, Vec::new());
461
462 tier.set_intervals(
463 vec![
464 Interval::new(0.0, 1.0, "test".to_string()),
465 Interval::new(1.0, 2.0, "test".to_string()),
466 ],
467 Some(true),
468 );
469
470 assert_eq!(tier.intervals.len(), 2);
471 }
472
473 #[test]
474 #[allow(clippy::float_cmp)]
475 fn reorder() {
476 let mut tier = Tier::new("test".to_string(), 0.0, 2.3, Vec::new());
477
478 tier.push_intervals(
479 vec![
480 Interval::new(1.0, 2.0, "test".to_string()),
481 Interval::new(0.0, 1.0, "test".to_string()),
482 ],
483 Some(true),
484 );
485
486 tier.reorder();
487
488 assert_eq!(tier.intervals[0].xmin, 0.0);
489 assert_eq!(tier.intervals[1].xmin, 1.0);
490 }
491
492 mod check_overlaps {
493 use crate::{
494 interval::{Interval, Tier as IntervalTier},
495 textgrid::{TextGrid, Tier},
496 };
497
498 #[test]
499 fn no_overlap() {
500 let mut textgrid = TextGrid::new(0.0, 2.3, Vec::new(), "test".to_string());
501
502 textgrid.push_tier(
503 Tier::IntervalTier(IntervalTier::new(
504 "John".to_string(),
505 0.0,
506 2.3,
507 vec![
508 Interval::new(0.0, 1.5, "daisy bell".to_string()),
509 Interval::new(1.5, 2.3, "daisy bell".to_string()),
510 ],
511 )),
512 false,
513 );
514
515 let overlaps = textgrid.check_overlaps();
516
517 assert!(overlaps.is_none());
518 }
519
520 #[test]
521 fn overlap() {
522 let mut textgrid = TextGrid::new(0.0, 2.3, Vec::new(), "test".to_string());
523
524 textgrid.push_tier(
525 Tier::IntervalTier(IntervalTier::new(
526 "John".to_string(),
527 0.0,
528 2.3,
529 vec![
530 Interval::new(0.0, 1.5, "daisy bell".to_string()),
531 Interval::new(1.0, 2.3, "daisy bell".to_string()),
532 ],
533 )),
534 false,
535 );
536
537 let overlaps = textgrid.check_overlaps().unwrap();
538
539 assert_eq!(overlaps.len(), 1);
540 assert_eq!(overlaps[0].0, "John");
541 assert_eq!(overlaps[0].1, (0, 1));
542 }
543 }
544
545 #[allow(clippy::float_cmp)]
546 mod fix_boundaries {
547 use crate::interval::{Interval, Tier};
548
549 #[test]
550 fn prefer_first() {
551 let mut tier = Tier::new("test".to_string(), 0.0, 2.3, Vec::new());
552
553 tier.push_intervals(
554 vec![
555 Interval::new(0.0, 1.2, "daisy".to_string()),
556 Interval::new(1.0, 1.75, "bell".to_string()),
557 Interval::new(1.5, 2.5, "answer".to_string()),
558 Interval::new(2.0, 5.0, "do".to_string()),
559 ],
560 false,
561 );
562
563 tier.fix_boundaries(true);
564
565 assert_eq!(tier.intervals()[0].xmin(), &0.0);
566 assert_eq!(tier.intervals()[0].xmax(), &1.2);
567 assert_eq!(tier.intervals()[1].xmin(), &1.2);
568 assert_eq!(tier.intervals()[1].xmax(), &1.75);
569 assert_eq!(tier.intervals()[2].xmin(), &1.75);
570 assert_eq!(tier.intervals()[2].xmax(), &2.5);
571 assert_eq!(tier.intervals()[3].xmin(), &2.5);
572 assert_eq!(tier.intervals()[3].xmax(), &5.0);
573 }
574
575 #[test]
576 fn prefer_last() {
577 let mut tier = Tier::new("test".to_string(), 0.0, 2.3, Vec::new());
578
579 tier.push_intervals(
580 vec![
581 Interval::new(0.0, 1.2, "daisy".to_string()),
582 Interval::new(1.0, 1.75, "bell".to_string()),
583 Interval::new(1.5, 2.5, "answer".to_string()),
584 Interval::new(2.0, 5.0, "do".to_string()),
585 ],
586 false,
587 );
588
589 tier.fix_boundaries(false);
590
591 assert_eq!(tier.intervals()[0].xmin(), &0.0);
592 assert_eq!(tier.intervals()[0].xmax(), &1.0);
593 assert_eq!(tier.intervals()[1].xmin(), &1.0);
594 assert_eq!(tier.intervals()[1].xmax(), &1.5);
595 assert_eq!(tier.intervals()[2].xmin(), &1.5);
596 assert_eq!(tier.intervals()[2].xmax(), &2.0);
597 assert_eq!(tier.intervals()[3].xmin(), &2.0);
598 assert_eq!(tier.intervals()[3].xmax(), &5.0);
599 }
600 }
601
602 #[test]
603 #[allow(clippy::float_cmp)]
604 fn fill_gaps() {
605 let mut tier = Tier::new("test".to_string(), 0.0, 2.3, Vec::new());
606
607 tier.push_intervals(
608 vec![
609 Interval::new(0.0, 1.2, "daisy".to_string()),
610 Interval::new(1.5, 2.3, "bell".to_string()),
611 ],
612 false,
613 );
614
615 tier.fill_gaps("gap");
616
617 assert_eq!(tier.intervals()[1].text(), "gap");
618 assert_eq!(tier.intervals()[1].xmin(), &1.2);
619 assert_eq!(tier.intervals()[1].xmax(), &1.5);
620 }
621
622 #[test]
623 fn to_string() {
624 let tier = Tier::new("test".to_string(), 0.0, 2.3, Vec::new());
625
626 assert_eq!(
627 tier.to_string(),
628 "IntervalTier test:
629 xmin: 0
630 xmax: 2.3
631 interval count: 0"
632 );
633 }
634}