use std::ops::Range;
use crate::geometry::Vec2;
use crate::raster::{RasterImage, RasterResidency, Resolution};
use crate::render_context::RenderContext;
use crate::time::{LocalTime, Time};
use super::*;
#[derive(Debug, Clone, Copy, crate::Keyable)]
pub struct Placement {
start: f64,
end: Option<f64>,
fill: bool,
}
impl Placement {
pub fn start(&self) -> f64 {
self.start
}
pub fn end(&self) -> Option<f64> {
self.end
}
pub fn is_fill(&self) -> bool {
self.fill
}
fn fill() -> Self {
Self {
start: 0.0,
end: None,
fill: true,
}
}
}
impl From<f64> for Placement {
fn from(start: f64) -> Self {
Self {
start,
end: None,
fill: false,
}
}
}
impl From<Range<f64>> for Placement {
fn from(window: Range<f64>) -> Self {
Self {
start: window.start,
end: Some(window.end),
fill: false,
}
}
}
#[derive(Clone, crate::Keyable)]
pub struct Placed {
placement: Placement,
child: Box<dyn TimelineComponent + Send>,
}
impl Placed {
pub fn new(placement: Placement, child: Box<dyn TimelineComponent + Send>) -> Self {
Self { placement, child }
}
pub fn placement(&self) -> Placement {
self.placement
}
pub fn is_fill(&self) -> bool {
self.placement.fill
}
pub fn child(&self) -> &(dyn TimelineComponent + Send) {
&*self.child
}
pub fn speed(&self) -> f64 {
self.speed_with_fill_length(None)
}
fn parent_window_length(&self, fill_length: Option<f64>) -> Option<f64> {
if self.is_fill() {
fill_length
} else {
self.placement.end.map(|end| end - self.placement.start)
}
}
fn speed_with_fill_length(&self, fill_length: Option<f64>) -> f64 {
match (self.parent_window_length(fill_length), self.child.measure()) {
(Some(window), Some(content)) if window > 0.0 => content / window,
_ => 1.0,
}
}
fn resolve_with_fill_length(
&self,
abs_start: f64,
fill_length: Option<f64>,
out: &mut ResolveCtx,
) -> f64 {
if let Some(end) = self.placement.end {
if end - self.placement.start <= 0.0 {
out.error(format!(
"a placement window must have positive length, got .at({}..{})",
self.placement.start, end
));
}
}
let scale = out.local_scale;
let child_abs = abs_start + self.placement.start * scale;
let saved = out.local_scale;
out.local_scale = scale / self.speed_with_fill_length(fill_length);
let child_len = self.child.resolve(child_abs, out);
out.local_scale = saved;
fill_length.unwrap_or_else(|| self.duration().unwrap_or(self.placement.start + child_len))
}
pub(crate) fn resolve_fill(
&self,
abs_start: f64,
fill_length: f64,
out: &mut ResolveCtx,
) -> f64 {
debug_assert!(self.is_fill());
self.resolve_with_fill_length(abs_start, Some(fill_length), out)
}
fn frame_with_fill_length(
&self,
clock: Clock<'_>,
fill_length: Option<f64>,
canvas: Vec2,
target: Resolution,
residency: RasterResidency,
ctx: &mut dyn RenderContext,
) -> Option<RasterImage> {
let t = clock.local().seconds();
let speed = self.speed_with_fill_length(fill_length);
let parent_window = self.parent_window_length(fill_length);
let active = match parent_window {
Some(window) => t >= self.placement.start && t < self.placement.start + window,
None if self.is_fill() => true,
None => match self.child.measure() {
Some(duration) => {
t >= self.placement.start && t < self.placement.start + duration / speed
}
None => true,
},
};
if !active {
return None;
}
let rebased = (t - self.placement.start) * speed;
let window = if self.is_fill() {
match (fill_length, self.child.measure()) {
(Some(length), Some(_)) => Some(length * speed),
_ => None,
}
} else {
match self.placement.end {
Some(end) => Some((end - self.placement.start) * speed),
None => self.child.measure(),
}
};
let child_clock = clock.with_local_window(LocalTime::new(rebased), window);
self.child
.frame(child_clock, canvas, target, residency, ctx)
}
pub(crate) fn frame_fill(
&self,
clock: Clock<'_>,
fill_length: f64,
canvas: Vec2,
target: Resolution,
residency: RasterResidency,
ctx: &mut dyn RenderContext,
) -> Option<RasterImage> {
debug_assert!(self.is_fill());
self.frame_with_fill_length(clock, Some(fill_length), canvas, target, residency, ctx)
}
fn render_audio_block_with_fill_length(
&self,
mut block: AudioBlockMut<'_>,
fill_length: Option<f64>,
ctx: &mut AudioRenderContext,
) {
let request = block.request();
let speed = self.speed_with_fill_length(fill_length);
let parent_window = self.parent_window_length(fill_length);
let active_end = match parent_window {
Some(window) => Some(self.placement.start + window),
None if self.is_fill() => None,
None => self
.child
.measure()
.map(|duration| self.placement.start + duration / speed),
};
if active_end.is_some_and(|end| !request.may_overlap_local(self.placement.start, end)) {
block.clear();
return;
}
let child_request = request.with_local_timing(
(request.local_start() - self.placement.start) * speed,
request.local_step() * speed,
);
let mut scratch = ctx.take_scratch(request.sample_len());
self.child
.render_audio_block(AudioBlockMut::new(child_request, &mut scratch), ctx);
block.clear();
let channels = request.channels() as usize;
for frame in 0..request.frame_count() {
let t = request.time_at(frame);
let active = active_end.is_none_or(|end| t >= self.placement.start && t < end);
if active {
let base = frame * channels;
block.samples_mut()[base..base + channels]
.copy_from_slice(&scratch[base..base + channels]);
}
}
ctx.recycle_scratch(scratch);
}
pub(crate) fn render_audio_fill(
&self,
block: AudioBlockMut<'_>,
fill_length: f64,
ctx: &mut AudioRenderContext,
) {
debug_assert!(self.is_fill());
self.render_audio_block_with_fill_length(block, Some(fill_length), ctx);
}
fn cues_with_fill_length(&self, offset: f64, fill_length: Option<f64>) -> Vec<Cue> {
let child_offset = offset + self.placement.start;
let inverse_speed = 1.0 / self.speed_with_fill_length(fill_length);
let mut cues = self.child.cues(0.0);
for cue in &mut cues {
cue.start = child_offset + cue.start * inverse_speed;
cue.end = child_offset + cue.end * inverse_speed;
}
if self.child.measure().is_none() {
let parent_end = if self.is_fill() {
fill_length.map(|length| self.placement.start + length)
} else {
self.placement.end
};
if let Some(end) = parent_end {
let abs_end = offset + end;
for cue in &mut cues {
cue.end = abs_end;
}
}
}
cues
}
pub(crate) fn cues_fill(&self, offset: f64, fill_length: f64) -> Vec<Cue> {
debug_assert!(self.is_fill());
self.cues_with_fill_length(offset, Some(fill_length))
}
fn arrangement_with_fill_length(&self, offset: f64, fill_length: Option<f64>) -> Arrangement {
let child_offset = offset + self.placement.start;
let mut node = self.child.arrangement(0.0);
map_arrangement_time(
&mut node,
child_offset,
1.0 / self.speed_with_fill_length(fill_length),
);
if self.child.measure().is_none() {
let parent_end = if self.is_fill() {
fill_length.map(|length| self.placement.start + length)
} else {
self.placement.end
};
if let Some(end) = parent_end {
node.end = offset + end;
}
}
node
}
pub(crate) fn arrangement_fill(&self, offset: f64, fill_length: f64) -> Arrangement {
debug_assert!(self.is_fill());
self.arrangement_with_fill_length(offset, Some(fill_length))
}
}
impl TimelineComponent for Placed {
fn duration(&self) -> Option<f64> {
match self.placement.end {
Some(end) => Some(end - self.placement.start),
None => self.child.measure(),
}
}
fn measure(&self) -> Option<f64> {
match self.placement.end {
Some(end) => Some(end),
None => self
.child
.measure()
.map(|inner| self.placement.start + inner),
}
}
fn resolve(&self, abs_start: f64, out: &mut ResolveCtx) -> f64 {
self.resolve_with_fill_length(abs_start, None, out)
}
fn frame(
&self,
clock: Clock<'_>,
canvas: Vec2,
target: Resolution,
residency: RasterResidency,
ctx: &mut dyn RenderContext,
) -> Option<RasterImage> {
self.frame_with_fill_length(clock, None, canvas, target, residency, ctx)
}
fn render_audio_block(&self, block: AudioBlockMut<'_>, ctx: &mut AudioRenderContext) {
self.render_audio_block_with_fill_length(block, None, ctx);
}
fn cues(&self, offset: f64) -> Vec<Cue> {
self.cues_with_fill_length(offset, None)
}
fn arrangement(&self, offset: f64) -> Arrangement {
self.arrangement_with_fill_length(offset, None)
}
}
fn map_arrangement_time(node: &mut Arrangement, offset: f64, scale: f64) {
node.start = offset + node.start * scale;
node.end = offset + node.end * scale;
for trigger in &mut node.triggers {
trigger.time = offset + trigger.time * scale;
}
for child in &mut node.children {
map_arrangement_time(child, offset, scale);
}
}
impl From<Placed> for Box<dyn TimelineComponent + Send> {
fn from(placed: Placed) -> Self {
Box::new(placed)
}
}
pub trait Timed: TimelineComponent + Sized + Send + 'static {
fn at(self, placement: impl Into<Placement>) -> Placed {
Placed::new(placement.into(), Box::new(self))
}
fn fill(self) -> Placed {
Placed::new(Placement::fill(), Box::new(self))
}
fn trim<R>(self, bounds: R) -> Trim<Self>
where
R: TrimBounds,
Self: PartialEq + std::hash::Hash,
{
Trim::new(self, bounds)
}
}
impl<T: TimelineComponent + Send + 'static> Timed for T {}
pub trait TimedBuilder: TimelineBuilder
where
Self::Output: Send,
{
fn at(self, placement: impl Into<Placement>) -> Placed {
self.build_component().at(placement)
}
fn fill(self) -> Placed {
self.build_component().fill()
}
fn trim<R>(self, bounds: R) -> Trim<Self::Output>
where
R: TrimBounds,
{
self.build_component().trim(bounds)
}
}
impl<B> TimedBuilder for B
where
B: TimelineBuilder,
B::Output: Send,
{
}