use crate::{ActiveIndex, EvalError, IndexedClip, collect_overlapping, count_overlapping};
use alpha2_media::SourceId;
use alpha2_model::{AudioProperties, ClipId, Project, TimeMap, TrackId};
use alpha2_time::{MediaTime, TimeRange};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ActiveAudioClip<'a> {
pub clip_id: &'a ClipId,
pub source_id: &'a SourceId,
pub track_id: &'a TrackId,
pub track_order: usize,
pub clip_order: usize,
pub visual_track: bool,
pub timeline_range: TimeRange,
pub source_range: TimeRange,
pub time_map: TimeMap,
pub active_range: TimeRange,
pub audio: &'a AudioProperties,
}
impl ActiveAudioClip<'_> {
#[must_use]
pub fn speed(&self) -> f64 {
match self.time_map {
TimeMap::Loop { .. } => return 1.0,
TimeMap::Freeze { .. } => return 0.0,
TimeMap::Forward | TimeMap::Reverse => {}
}
let timeline = self.timeline_range.duration().map_or(0, MediaTime::ticks);
let source = self.source_range.duration().map_or(0, MediaTime::ticks);
if timeline <= 0 || source <= 0 {
return 1.0;
}
#[allow(clippy::cast_precision_loss)]
{
source as f64 / timeline as f64
}
}
#[must_use]
pub fn source_time_at(&self, time: MediaTime) -> Option<MediaTime> {
crate::map_source_time(
self.timeline_range,
self.source_range,
self.time_map,
crate::Mirror::TICK,
time,
)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct AudioSpanDesc<'a> {
pub range: TimeRange,
pub clips: Vec<ActiveAudioClip<'a>>,
}
pub fn evaluate_audio<'a>(
project: &Project,
index: &'a ActiveIndex,
range: TimeRange,
) -> Result<AudioSpanDesc<'a>, EvalError> {
crate::ensure_index_matches(project, index)?;
let capacity = index.root.map_or(0, |root| {
count_overlapping(&index.nodes, root, range, &|entry| entry.source_is_audible)
});
let mut scratch = AudioSpanScratch {
nodes: Vec::with_capacity(capacity),
};
let span = fill_span(index, range, &mut scratch)?;
Ok(AudioSpanDesc {
range,
clips: span.iter().collect(),
})
}
#[derive(Clone, Debug, Default)]
pub struct AudioSpanScratch {
nodes: Vec<usize>,
}
#[derive(Clone, Copy, Debug)]
pub struct AudioSpan<'a, 's> {
index: &'a ActiveIndex,
range: TimeRange,
nodes: &'s [usize],
}
impl<'a> AudioSpan<'a, '_> {
#[must_use]
pub const fn range(&self) -> TimeRange {
self.range
}
#[must_use]
pub const fn len(&self) -> usize {
self.nodes.len()
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub fn iter(&self) -> impl ExactSizeIterator<Item = ActiveAudioClip<'a>> {
let (index, range) = (self.index, self.range);
self.nodes
.iter()
.map(move |&node| active_clip(index.nodes[node].entry(), range))
}
}
pub fn evaluate_audio_in<'a, 's>(
project: &Project,
index: &'a ActiveIndex,
range: TimeRange,
scratch: &'s mut AudioSpanScratch,
) -> Result<AudioSpan<'a, 's>, EvalError> {
crate::ensure_index_matches(project, index)?;
fill_span(index, range, scratch)
}
fn fill_span<'a, 's>(
index: &'a ActiveIndex,
range: TimeRange,
scratch: &'s mut AudioSpanScratch,
) -> Result<AudioSpan<'a, 's>, EvalError> {
let nodes = &mut scratch.nodes;
nodes.clear();
let mix_order = |entry: &IndexedClip| {
(
entry.track_order,
entry.timeline_range.start(),
entry.clip_order,
)
};
let mut unmappable: Option<&IndexedClip> = None;
if let Some(root) = index.root {
collect_overlapping(&index.nodes, root, range, &mut |node, entry| {
if !entry.source_is_audible {
return;
}
let Some(active_range) = entry.timeline_range.intersection(range) else {
return;
};
if active_range.is_empty() {
return;
}
if crate::map_source_time(
entry.timeline_range,
entry.source_range,
entry.time_map,
crate::Mirror::TICK,
active_range.start(),
)
.is_none()
{
if unmappable.is_none_or(|first| mix_order(entry) < mix_order(first)) {
unmappable = Some(entry);
}
return;
}
nodes.push(node);
});
}
if let Some(entry) = unmappable {
return Err(EvalError::UnmappableClip {
clip: entry.clip_id.clone(),
});
}
nodes.sort_unstable_by_key(|&node| mix_order(index.nodes[node].entry()));
Ok(AudioSpan {
index,
range,
nodes,
})
}
fn active_clip(entry: &IndexedClip, range: TimeRange) -> ActiveAudioClip<'_> {
ActiveAudioClip {
clip_id: &entry.clip_id,
source_id: &entry.source_id,
track_id: &entry.track_id,
track_order: entry.track_order,
clip_order: entry.clip_order,
visual_track: entry.track_kind == alpha2_model::TrackKind::Video,
timeline_range: entry.timeline_range,
source_range: entry.source_range,
time_map: entry.time_map,
active_range: entry.timeline_range.intersection(range).unwrap_or(range),
audio: &entry.audio,
}
}