use std::collections::HashMap;
use crate::geometry::Vec2;
use crate::raster::{RasterImage, Resolution};
use crate::render_context::RenderContext;
use crate::time::{LocalTime, Time, TimelineTime};
use super::*;
#[derive(Debug, Clone, Default)]
pub struct TriggerTable {
map: HashMap<u64, f32>,
}
impl TriggerTable {
pub fn new() -> Self {
Self::default()
}
pub fn record(&mut self, event: Event, time: f32) {
self.record_id(event.id(), time);
}
pub fn record_id(&mut self, id: u64, time: f32) {
self.map
.entry(id)
.and_modify(|t| {
if time < *t {
*t = time;
}
})
.or_insert(time);
}
pub fn get(&self, id: u64) -> TimelineTime {
TimelineTime::new(self.map.get(&id).copied().unwrap_or(f32::INFINITY))
}
pub fn contains(&self, id: u64) -> bool {
self.map.contains_key(&id)
}
}
#[derive(Debug)]
pub struct ResolveCtx {
triggers: TriggerTable,
abs_start: f32,
warnings: Vec<String>,
errors: Vec<String>,
pub(super) local_scale: f32,
}
impl Default for ResolveCtx {
fn default() -> Self {
Self {
triggers: TriggerTable::default(),
abs_start: 0.0,
warnings: Vec::new(),
errors: Vec::new(),
local_scale: 1.0,
}
}
}
impl ResolveCtx {
pub fn new() -> Self {
Self::default()
}
pub fn triggers(&self) -> &TriggerTable {
&self.triggers
}
pub fn triggers_mut(&mut self) -> &mut TriggerTable {
&mut self.triggers
}
pub fn abs_start(&self) -> f32 {
self.abs_start
}
pub fn local_scale(&self) -> f32 {
self.local_scale
}
pub fn set_abs_start(&mut self, abs_start: f32) {
self.abs_start = abs_start;
}
pub fn warn(&mut self, message: impl Into<String>) {
self.warnings.push(message.into());
}
pub fn warnings(&self) -> &[String] {
&self.warnings
}
pub fn error(&mut self, message: impl Into<String>) {
self.errors.push(message.into());
}
pub fn errors(&self) -> &[String] {
&self.errors
}
pub fn into_triggers(self) -> TriggerTable {
self.triggers
}
pub fn into_parts(self) -> (TriggerTable, Vec<String>, Vec<String>) {
(self.triggers, self.warnings, self.errors)
}
}
#[derive(Debug, thiserror::Error)]
pub enum ResolveError {
#[error("media probe failed: {0}")]
Probe(String),
#[error("root timeline is timeless: place media or an explicit window to give it a length")]
Timeless,
#[error("invalid timeline arrangement: {0}")]
Invalid(String),
}
pub struct ResolvedTimeline {
source: Box<dyn TimelineComponent + Send>,
triggers: TriggerTable,
duration: f32,
warnings: Vec<String>,
canvas: Vec2,
}
const _: fn() = || {
fn assert_send<T: Send>() {}
assert_send::<ResolvedTimeline>();
};
impl ResolvedTimeline {
pub fn source(&self) -> &(dyn TimelineComponent + Send) {
&*self.source
}
pub fn triggers(&self) -> &TriggerTable {
&self.triggers
}
pub fn duration(&self) -> f32 {
self.duration
}
pub fn canvas(&self) -> Vec2 {
self.canvas
}
pub fn warnings(&self) -> &[String] {
&self.warnings
}
pub fn frame(
&self,
t: TimelineTime,
target: Resolution,
ctx: &mut dyn RenderContext,
) -> Option<RasterImage> {
let clock = Clock::new(t, LocalTime::new(t.seconds()), self.triggers())
.with_local_window(LocalTime::new(t.seconds()), Some(self.duration));
self.source().frame(clock, self.canvas, target, ctx)
}
pub fn samples(&self, t: TimelineTime, window: f32) -> Option<AudioBuffer> {
let clock = Clock::new(t, LocalTime::new(t.seconds()), self.triggers());
self.source().samples(clock, window)
}
pub fn render_audio(&self, rate: u32, channels: u16) -> AudioBuffer {
let mut mix = crate::audio::AudioMix::new(self.duration, rate, channels);
self.source().mix_into(&mut mix, 0.0, 1.0);
mix.into_buffer()
}
pub fn render_audio_window(
&self,
start: f32,
duration: f32,
rate: u32,
channels: u16,
) -> AudioBuffer {
let start = start.max(0.0);
let duration = duration.max(0.0);
let mut mix = crate::audio::AudioMix::new(duration, rate, channels);
self.source().mix_into(&mut mix, -start, 1.0);
mix.into_buffer()
}
}
impl std::fmt::Debug for ResolvedTimeline {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ResolvedTimeline")
.field("duration", &self.duration)
.field("triggers", &self.triggers)
.field("warnings", &self.warnings)
.finish_non_exhaustive()
}
}
pub fn resolve(
root: impl TimelineComponent + Send + 'static,
) -> Result<ResolvedTimeline, ResolveError> {
resolve_with_canvas(root, DEFAULT_CANVAS)
}
pub const DEFAULT_CANVAS: Vec2 = Vec2(1920.0, 1080.0);
pub fn resolve_with_canvas(
root: impl TimelineComponent + Send + 'static,
canvas: Vec2,
) -> Result<ResolvedTimeline, ResolveError> {
let source: Box<dyn TimelineComponent + Send> = Box::new(root);
let duration = source.measure().ok_or(ResolveError::Timeless)?;
let mut ctx = ResolveCtx::new();
source.resolve(0.0, &mut ctx);
let (triggers, warnings, errors) = ctx.into_parts();
if let Some(first) = errors.into_iter().next() {
return Err(ResolveError::Invalid(first));
}
Ok(ResolvedTimeline {
source,
triggers,
duration,
warnings,
canvas,
})
}