use std::collections::HashMap;
use crate::geometry::Vec2;
use crate::raster::{RasterImage, RasterResidency, Resolution};
use crate::render_context::RenderContext;
use crate::time::{LocalTime, Time, TimelineTime};
use super::*;
#[derive(Debug, Clone, Default)]
pub struct TriggerTable {
map: HashMap<u64, f64>,
}
impl TriggerTable {
pub fn new() -> Self {
Self::default()
}
pub fn record(&mut self, event: Event, time: f64) {
self.record_id(event.id(), time);
}
pub fn record_id(&mut self, id: u64, time: f64) {
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(f64::INFINITY))
}
pub fn contains(&self, id: u64) -> bool {
self.map.contains_key(&id)
}
fn merge_in_range(&mut self, other: Self, range: std::ops::Range<f64>) {
for (id, time) in other.map {
if time >= range.start && time <= range.end {
self.record_id(id, time);
}
}
}
}
#[derive(Debug)]
pub struct ResolveCtx {
triggers: TriggerTable,
abs_start: f64,
warnings: Vec<String>,
errors: Vec<String>,
pub(super) local_scale: f64,
}
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) -> f64 {
self.abs_start
}
pub fn local_scale(&self) -> f64 {
self.local_scale
}
pub fn set_abs_start(&mut self, abs_start: f64) {
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(crate) fn resolve_trimmed(
&mut self,
child: &dyn TimelineComponent,
child_abs_start: f64,
output_range: std::ops::Range<f64>,
) -> f64 {
let parent_triggers = std::mem::take(&mut self.triggers);
let child_len = child.resolve(child_abs_start, self);
let child_triggers = std::mem::replace(&mut self.triggers, parent_triggers);
self.triggers.merge_in_range(child_triggers, output_range);
child_len
}
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: f64,
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) -> f64 {
self.duration
}
pub fn canvas(&self) -> Vec2 {
self.canvas
}
pub fn warnings(&self) -> &[String] {
&self.warnings
}
pub fn frame(
&self,
t: TimelineTime,
target: Resolution,
residency: RasterResidency,
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, residency, ctx)
}
pub fn render_audio(&self, rate: u32, channels: u16) -> AudioBuffer {
let frame_count = frames_for_duration(self.duration, rate);
self.render_audio_frames(0, frame_count, rate, channels)
}
pub fn render_audio_window(
&self,
start: f64,
duration: f64,
rate: u32,
channels: u16,
) -> AudioBuffer {
let start_frame = seconds_to_frame(start.max(0.0), rate);
let frame_count = frames_for_duration(duration, rate);
self.render_audio_frames(start_frame, frame_count, rate, channels)
}
fn render_audio_frames(
&self,
start_frame: i64,
frame_count: usize,
rate: u32,
channels: u16,
) -> AudioBuffer {
const BLOCK_FRAMES: usize = 4_096;
let rate = rate.max(1);
let channels = channels.max(1);
let mut samples = vec![0.0; frame_count.saturating_mul(channels as usize)];
let mut ctx = AudioRenderContext::default();
let root_request = AudioRenderRequest::new(start_frame, frame_count, rate, channels);
let mut rendered = 0usize;
while rendered < frame_count {
let block_frames = (frame_count - rendered).min(BLOCK_FRAMES);
let request = root_request.subrange(rendered, block_frames);
let sample_start = rendered * channels as usize;
let sample_end = sample_start + request.sample_len();
self.source().render_audio_block(
AudioBlockMut::new(request, &mut samples[sample_start..sample_end]),
&mut ctx,
);
rendered += block_frames;
}
for frame in 0..frame_count {
let absolute_frame = start_frame.saturating_add(frame as i64);
let t = absolute_frame as f64 / rate as f64;
if t < 0.0 || t >= self.duration {
let base = frame * channels as usize;
samples[base..base + channels as usize].fill(0.0);
}
}
AudioBuffer {
samples,
rate,
channels,
}
}
}
fn seconds_to_frame(seconds: f64, rate: u32) -> i64 {
if !seconds.is_finite() {
return 0;
}
(seconds * rate.max(1) as f64).round() as i64
}
fn frames_for_duration(duration: f64, rate: u32) -> usize {
if !duration.is_finite() || duration <= 0.0 {
return 0;
}
(duration * rate.max(1) as f64).ceil() as usize
}
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,
})
}