Skip to main content

alpha2_engine/
audio.rs

1//! Project-driven audio mixing.
2//!
3//! [`AudioRenderer`] turns a project snapshot and host-decoded source blocks
4//! into one mixed, project-rate planar block. It is the audio counterpart of
5//! rendering an evaluated frame: evaluation decides *what* is audible, the
6//! host owns decode, and this renderer owns gain, panning, fades, effects,
7//! bus summing, and limiting.
8//!
9//! Signal order per clip is deterministic and fixed:
10//! source -> resample -> clip effects -> clip gain/fades -> pan -> track bus.
11//! Placing gain and fades after the clip effects lets a fade reach true
12//! silence even when an effect has a tail.
13//!
14//! Track buses sum into the master bus. A ducked track reads the *pre-duck*
15//! output of its sidechain track, so the mix never depends on track order.
16//! A bus that no clip, effect, or ducker writes in a block is silent and
17//! costs nothing there: it is neither cleared nor summed. Only a bus that
18//! ducks holds a second block for its ducked output.
19
20use std::collections::HashMap;
21
22use alpha2_audio::{
23    Biquad, BiquadKind, ChannelLayout, Compressor, Ducker, Interpolator, LoudnessMeter,
24    LoudnessReport, PeakLimiter, ProcessCtx, Processor, ProcessorChain, SchroederReverb,
25    TransportState,
26};
27use alpha2_eval::{ActiveAudioClip, AudioSpanScratch, sample_scalar};
28use alpha2_media::{AudioBlock, AudioBlockProvider, SourceId, SpeakerLayout};
29use alpha2_model::{
30    Animated, AudioEffect, ClipId, EqBandKind, LOOP_FADE_SECONDS, LoopMode, MAX_GAIN, MasterAudio,
31    TimeMap, TrackAudio, TrackId,
32};
33use alpha2_time::{MediaTime, TICKS_PER_SECOND, TimeRange};
34
35use crate::downmix::{ChannelMix, Downmix};
36use crate::{EngineError, EngineProject};
37
38/// The most interpolation weights a retimed read keeps for the fractions
39/// its positions repeat (256 KiB).
40const MAX_CACHED_WEIGHTS: usize = 1 << 15;
41
42/// Largest block a renderer will mix in one call.
43pub const MAX_AUDIO_BLOCK: usize = 8_192;
44
45// The model counts a clip's effects as built from a margin before the lookahead before its start.
46// A block mixing a clip builds its chain and the next that does not drops it, so the margin holds
47// one block of the largest size at the lowest rate, the sample each end of a clip rounds to, and
48// the half sample each lookahead of the clip's chain and its track's rounds to.
49const _: () = {
50    let rate = *alpha2_model::SAMPLE_RATE_RANGE.start() as usize;
51    let samples = MAX_AUDIO_BLOCK + 2 + alpha2_model::MAX_CHAIN_EFFECTS;
52    assert!(
53        samples * 1_000 <= alpha2_model::AUDIO_EFFECT_STATE_MARGIN_MS as usize * rate,
54        "AUDIO_EFFECT_STATE_MARGIN_MS must hold a largest block at the lowest rate"
55    );
56};
57
58/// Longest a stage replays to settle before a seek target, in seconds.
59///
60/// A recursive effect's settling horizon grows without bound as it nears
61/// infinite memory (a reverb's damping near one, a compressor's release of
62/// seconds), and a stage settles after everything downstream of it. Each
63/// stage's lead is capped here, so a seek replays at most this much audio
64/// and asks the host for at most this much source history plus the graph's
65/// lookahead. A stage whose horizon is longer starts this far back and
66/// approximates its settled state, as every finite warm-up does.
67pub const MAX_SETTLING_SECONDS: u32 = 30;
68
69/// What [`AudioRenderer::render`] does with a block that does not start
70/// where the previous one ended.
71#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
72pub enum AudioContinuity {
73    /// Every such block resets the renderer and replays the settling history
74    /// before it, so it mixes what a fresh renderer would: a block's output
75    /// depends only on where its run of consecutive blocks began, never on
76    /// what was mixed before that. Exports, loudness measurements, and any
77    /// output that must repeat use it.
78    #[default]
79    Exact,
80    /// For live preview only. A block that starts at most
81    /// [`AudioRenderer::max_block`] samples before or after where the
82    /// previous one ended continues from the state that block left, as if it
83    /// began where that one ended: nothing is reset or replayed, and the
84    /// block's input follows the previous block's into every processor
85    /// whichever way it stepped (a backward step feeds the samples it covers
86    /// again after those it already fed). Any longer jump, and the first
87    /// block after a reset, is exact. A small step (a block mixed again once
88    /// its media arrived, a transport nudging the playhead) then costs one
89    /// block instead of a replay of up to [`MAX_SETTLING_SECONDS`]; the mix
90    /// after it carries tails and envelopes across the step, so it is not
91    /// what a fresh renderer would mix there. It is still determined by the
92    /// blocks asked for, in order.
93    ContinueSmallGaps,
94}
95
96/// Outcome of mixing one block.
97#[derive(Clone, Debug, PartialEq, Eq)]
98pub struct AudioRenderStatus {
99    /// Sample index of the first mixed sample.
100    pub start_sample: i64,
101    /// Number of mixed samples per channel.
102    pub frames: usize,
103    /// Sources an audible clip needed but the provider could not supply.
104    ///
105    /// Those clips contributed silence, and the block was not metered. A host
106    /// decodes them and renders the same block again to obtain complete
107    /// audio: exactly the uninterrupted mix when the renderer holds
108    /// incomplete blocks ([`AudioRenderer::set_hold_incomplete_blocks`]), in
109    /// which case the whole block was silence and nothing advanced, and
110    /// otherwise the mix after a seek to it.
111    pub missing_sources: Vec<SourceId>,
112}
113
114impl AudioRenderStatus {
115    /// Returns whether every audible clip was mixed from real samples.
116    #[must_use]
117    pub fn is_complete(&self) -> bool {
118        self.missing_sources.is_empty()
119    }
120}
121
122/// A source interval a host decodes before a render reads it.
123#[derive(Clone, Debug, PartialEq, Eq)]
124pub struct AudioNeed {
125    /// Source to decode or retrieve.
126    pub source_id: SourceId,
127    /// Half-open source interval, in ticks, holding every sample the render
128    /// reads, interpolation neighbours included.
129    pub source_range: TimeRange,
130}
131
132struct TrackBus {
133    id: TrackId,
134    audio: TrackAudio,
135    chain: ProcessorChain,
136    ducker: Option<BusDucker>,
137    sidechain: Option<usize>,
138    /// The bus's samples for the block, valid only while [`Self::live`].
139    output: [Vec<f32>; 2],
140    /// Whether `output` holds this block's samples: a clip, the bus chain,
141    /// or the ducker wrote it. A bus nothing reaches in a block is silent;
142    /// its buffer keeps a stale block, is neither cleared nor summed, and is
143    /// cleared only when a stage needs to read or add to it
144    /// ([`Self::make_live`]).
145    live: bool,
146    /// Samples the bus chain delays its input by.
147    latency: i64,
148    /// One slot per clip on the track, in the track's order.
149    clips: Vec<ClipSlot>,
150    /// How long before a seek target the bus chain, and the mixing of clips
151    /// without a chain into it, must run to settle (see [`Leads`]).
152    chain_lead: i64,
153    /// The same for the ducker, when one runs.
154    ducker_lead: i64,
155    /// Whether the mix reads the bus's clips: the bus is heard, or it keys
156    /// the ducker of a bus that is (see [`read_buses`]).
157    read: bool,
158}
159
160impl TrackBus {
161    /// The bus whose output drives this bus's ducker, when both exist and
162    /// differ; `index` is this bus's own.
163    fn active_sidechain(&self, index: usize) -> Option<usize> {
164        self.ducker.as_ref()?;
165        self.sidechain.filter(|&sidechain| sidechain != index)
166    }
167
168    /// Makes `output` this block's: silence over the first `frames` samples
169    /// of every channel unless something already wrote the block.
170    fn make_live(&mut self, channels: usize, frames: usize) {
171        if self.live {
172            return;
173        }
174        for plane in self.output.iter_mut().take(channels) {
175            plane[..frames].fill(0.0);
176        }
177        self.live = true;
178        #[cfg(test)]
179        BUS_PLANES_CLEARED.set(BUS_PLANES_CLEARED.get() + channels);
180    }
181}
182
183/// A track's ducker and the buffer it writes the ducked block into, so only a
184/// bus that ducks holds a second block of samples.
185struct BusDucker {
186    ducker: Ducker,
187    output: [Vec<f32>; 2],
188}
189
190/// A clip of a track and, when it has effects, its chain.
191struct ClipSlot {
192    id: ClipId,
193    chain: Option<ClipChain>,
194}
195
196/// A clip's own effect chain.
197///
198/// Its processors exist only while the clip sounds: they are built when a
199/// block first mixes the clip and dropped before the first block that does
200/// not, whose gap would reset them anyway (see
201/// [`AudioRenderer::keep_mixed_chains`]). A project's state is then that of
202/// the clips one block mixes, which the model bounds
203/// ([`alpha2_model::Project::audio_effect_load`]), not of every clip on the
204/// timeline.
205struct ClipChain {
206    effects: Vec<AudioEffect>,
207    chain: Option<ProcessorChain>,
208    /// The block that last ran the chain, to find chains a block left idle.
209    ran_in: u64,
210    /// Samples the chain delays its input by.
211    latency: i64,
212    /// Samples the chain's nodes need to settle, excluding their delay.
213    history: u32,
214    /// Input position the chain continues from; anything else resets it.
215    next_input: Option<i64>,
216    /// How long before a seek target the chain must run to settle.
217    lead: i64,
218}
219
220/// Mixes a project's audio tracks into one project-rate block.
221pub struct AudioRenderer {
222    layout: ChannelLayout,
223    sample_rate: u32,
224    max_block: usize,
225    /// One bus per track, in project order, so a clip's track order is its
226    /// bus index.
227    buses: Vec<TrackBus>,
228    /// Buses by track ID, for a clip whose position does not match: one
229    /// evaluated from a snapshot other than the one the renderer was built
230    /// from.
231    bus_of_track: HashMap<TrackId, usize>,
232    /// Clip slots by clip ID, `(bus, position)`, for the same case.
233    slot_of_clip: HashMap<ClipId, (usize, usize)>,
234    /// Reused for every block's clip evaluation.
235    span_scratch: AudioSpanScratch,
236    master_chain: ProcessorChain,
237    /// Samples the master chain delays its input by.
238    master_latency: i64,
239    /// The longest latency from any clip's source to the output.
240    lookahead: i64,
241    /// Finite settling history in addition to latency compensation: the
242    /// longest lead of any stage.
243    history: i64,
244    /// How long before a seek target the master chain must run to settle.
245    master_lead: i64,
246    /// The same for the limiter.
247    limiter_lead: i64,
248    /// Every stage's lead, longest first and each once: where a replay
249    /// before a seek cuts its blocks, so each stage starts at its own sample
250    /// whatever the block size (see [`Self::replay_frames`]).
251    stage_leads: Vec<i64>,
252    master: MasterAudio,
253    limiter: Option<PeakLimiter>,
254    /// Static gain after the master gain and before the limiter: loudness
255    /// normalization. One (0 dB) unless set.
256    normalization_gain: f32,
257    hold_incomplete: bool,
258    continuity: AudioContinuity,
259    /// Measures what reaches the limiter, when metering is on.
260    meter: Option<LoudnessMeter>,
261    /// Measures what comes out, after the limiter, when output metering is on.
262    output_meter: Option<LoudnessMeter>,
263    any_solo: bool,
264    clip_a: [Vec<f32>; 2],
265    clip_b: [Vec<f32>; 2],
266    bus_in: [Vec<f32>; 2],
267    mix: [Vec<f32>; 2],
268    output: [Vec<f32>; 2],
269    /// The per-sample gain and left and right pan weights of one clip's or
270    /// bus's automation, or the master's gain: see [`fill_envelope`].
271    envelope: [Vec<f32>; 3],
272    /// Where the last successful render ended; `None` before the first.
273    next_sample: Option<i64>,
274    /// Slots, `(bus, position)`, whose clip chain is built.
275    live_chains: Vec<(usize, usize)>,
276    /// Counts mixed blocks, for [`ClipChain::ran_in`].
277    blocks_mixed: u64,
278    /// The interpolation weights of the position a retimed read is at, or of
279    /// every fraction its positions repeat.
280    weights: Vec<f64>,
281    /// Scratch for one fraction's weights while `weights` holds them all.
282    phase_weights: Vec<f64>,
283    /// The most clip effects, and their delay in ticks, built at once.
284    #[cfg(test)]
285    most_held: (usize, u64),
286    /// Project samples [`Self::read_source`] has read from sources.
287    #[cfg(test)]
288    samples_read: usize,
289}
290
291/// What [`AudioRenderer::fill_clip`] does with the source samples a clip
292/// reads.
293#[derive(Clone, Copy, Debug, PartialEq, Eq)]
294enum Fill {
295    /// Reads and resamples them into `clip_a`: a mix.
296    Samples,
297    /// Only checks `provider` supplies them, exactly as a mix would find
298    /// out, and reads none: a held render's check before it mixes.
299    Presence,
300}
301
302impl AudioRenderer {
303    /// Builds every track and master processor for a project snapshot, and
304    /// measures every clip's; a clip's are built when it is mixed.
305    ///
306    /// # Errors
307    /// Returns [`EngineError::InvalidAudioConfiguration`] for an unsupported
308    /// channel count, a zero sample rate, or an oversized block.
309    pub fn new(
310        snapshot: &EngineProject,
311        channels: usize,
312        max_block: usize,
313    ) -> Result<Self, EngineError> {
314        let layout =
315            ChannelLayout::from_channels(channels).ok_or(EngineError::InvalidAudioConfiguration)?;
316        let project = snapshot.project();
317        let sample_rate = project.settings.sample_rate;
318        if sample_rate == 0 || max_block == 0 || max_block > MAX_AUDIO_BLOCK {
319            return Err(EngineError::InvalidAudioConfiguration);
320        }
321
322        let (mut buses, bus_of_track) = track_buses(project, layout, sample_rate, max_block);
323        resolve_sidechains(&mut buses, &bus_of_track);
324        let slot_of_clip = buses
325            .iter()
326            .enumerate()
327            .flat_map(|(bus, track)| {
328                track
329                    .clips
330                    .iter()
331                    .enumerate()
332                    .map(move |(position, slot)| (slot.id.clone(), (bus, position)))
333            })
334            .collect();
335
336        let mut master_chain = ProcessorChain::new(layout);
337        push_effects(&mut master_chain, &project.audio.effects);
338        master_chain.prepare(sample_rate, max_block);
339        let limiter = project.audio.limiter.map(|limiter| {
340            let mut peak = PeakLimiter::new(limiter.threshold, limiter.release_ms);
341            peak.prepare(sample_rate, max_block);
342            peak
343        });
344        let any_solo = project.timeline.any_solo();
345        read_buses(&mut buses, any_solo);
346        let master_latency = i64::from(master_chain.latency_samples());
347        let lookahead = master_latency + longest_path_latency(&buses);
348        let settling = Settling(i64::from(sample_rate) * i64::from(MAX_SETTLING_SECONDS));
349        let limiter_lead = settling.lead(0, limiter.as_ref().map_or(0, Processor::history_samples));
350        let master_lead = settling.lead(limiter_lead, master_chain.history_samples());
351        let history = assign_leads(&mut buses, master_lead, settling).max(master_lead);
352        let stage_leads = stage_leads(&buses, master_lead, limiter_lead);
353
354        Ok(Self {
355            layout,
356            sample_rate,
357            max_block,
358            buses,
359            bus_of_track,
360            slot_of_clip,
361            span_scratch: AudioSpanScratch::default(),
362            master_chain,
363            master_latency,
364            lookahead,
365            history,
366            master_lead,
367            limiter_lead,
368            stage_leads,
369            master: project.audio.clone(),
370            limiter,
371            normalization_gain: 1.0,
372            hold_incomplete: false,
373            continuity: AudioContinuity::Exact,
374            meter: None,
375            output_meter: None,
376            any_solo,
377            clip_a: buffers(max_block),
378            clip_b: buffers(max_block),
379            bus_in: buffers(max_block),
380            mix: buffers(max_block),
381            output: buffers(max_block),
382            envelope: [
383                vec![0.0; max_block],
384                vec![0.0; max_block],
385                vec![0.0; max_block],
386            ],
387            next_sample: None,
388            live_chains: Vec::new(),
389            blocks_mixed: 0,
390            weights: Vec::new(),
391            phase_weights: Vec::new(),
392            #[cfg(test)]
393            most_held: (0, 0),
394            #[cfg(test)]
395            samples_read: 0,
396        })
397    }
398
399    /// Returns the project sample rate every block uses.
400    #[must_use]
401    pub const fn sample_rate(&self) -> u32 {
402        self.sample_rate
403    }
404
405    /// Returns the channel count of every mixed block.
406    #[must_use]
407    pub const fn channels(&self) -> usize {
408        self.layout.channels()
409    }
410
411    /// Returns the audio tracks this renderer mixes, in bus order.
412    #[must_use]
413    pub fn track_ids(&self) -> Vec<&TrackId> {
414        self.buses.iter().map(|bus| &bus.id).collect()
415    }
416
417    /// Returns the largest block this renderer accepts.
418    #[must_use]
419    pub const fn max_block(&self) -> usize {
420        self.max_block
421    }
422
423    /// Returns one mixed output channel from the most recent render.
424    #[must_use]
425    pub fn channel(&self, index: usize) -> &[f32] {
426        self.output.get(index).map_or(&[], Vec::as_slice)
427    }
428
429    /// How far ahead of a block, in samples, the renderer reads its sources.
430    ///
431    /// A lookahead effect (a compressor's `lookahead_ms`) delays what passes
432    /// through it. Rather than delay the mix, the renderer reads each path's
433    /// input early by the latency between it and the output, so the output
434    /// lines up with the timeline. Use [`Self::audio_needs`] for source supply:
435    /// a cold seek also needs recursive-state history, beyond this delay.
436    #[must_use]
437    pub fn required_lookahead_samples(&self) -> u32 {
438        u32::try_from(self.lookahead).unwrap_or(u32::MAX)
439    }
440
441    /// The source intervals rendering `frames` samples from `start_sample`
442    /// reads, for a host to decode first.
443    ///
444    /// They are what [`Self::render`] would read, found the way it reads
445    /// them: the same blocks (on a discontinuity it does not continue across
446    /// ([`AudioContinuity`]), the replay of each stage's settling history,
447    /// which stops at timeline zero, then the block), the
448    /// same clips (heard, on a bus the mix hears, once their stages have
449    /// begun settling), and the same source window per read, interpolation
450    /// neighbours included. A host that holds them needs no margin.
451    ///
452    /// Clips reading one source share a need where their intervals overlap or
453    /// touch; disjoint intervals of one source stay separate needs, so two
454    /// clips cut from far apart in one source do not ask for everything
455    /// between them.
456    ///
457    /// # Errors
458    /// Returns [`EngineError::InvalidAudioConfiguration`] for a negative
459    /// start, an empty block, one larger than [`Self::max_block`], or a block
460    /// whose timeline span does not fit in ticks, and an evaluation error when
461    /// the snapshot cannot be evaluated over a block's span.
462    pub fn audio_needs(
463        &self,
464        snapshot: &EngineProject,
465        start_sample: i64,
466        frames: usize,
467    ) -> Result<Vec<AudioNeed>, EngineError> {
468        self.check_block(start_sample, frames)?;
469        let mut needs = Vec::new();
470        let mut scratch = AudioSpanScratch::default();
471        if self.continues_at(start_sample).is_none() {
472            let mut position = self.preroll_start(start_sample)?;
473            while position < start_sample {
474                let count = self.replay_frames(position, start_sample);
475                self.block_needs(
476                    snapshot,
477                    position,
478                    count,
479                    Some(start_sample),
480                    &mut scratch,
481                    &mut needs,
482                )?;
483                position += i64::try_from(count).unwrap_or(i64::MAX);
484            }
485        }
486        self.block_needs(
487            snapshot,
488            start_sample,
489            frames,
490            None,
491            &mut scratch,
492            &mut needs,
493        )?;
494        Ok(needs)
495    }
496
497    /// Adds what one block of [`Self::mix_block`] reads to `needs`.
498    fn block_needs(
499        &self,
500        snapshot: &EngineProject,
501        start: i64,
502        frames: usize,
503        replay_target: Option<i64>,
504        scratch: &mut AudioSpanScratch,
505        needs: &mut Vec<AudioNeed>,
506    ) -> Result<(), EngineError> {
507        let frames_i64 = i64::try_from(frames).map_err(|_| EngineError::OutOfRange)?;
508        let stages = Stages {
509            target: replay_target,
510            block_end: start + frames_i64,
511            lookahead: self.lookahead,
512        };
513        let span = self.block_span(start, frames_i64)?;
514        let described = snapshot
515            .evaluate_audio_in(span, scratch)
516            .map_err(EngineError::Evaluate)?;
517        let master_input = start + self.master_latency;
518        let rate = self.sample_rate;
519        for clip in described.iter() {
520            let Some(read) = self.clip_read(&clip, master_input, frames, stages) else {
521                continue;
522            };
523            source_requests(&clip, read.first, read.window.content, rate, |from, to| {
524                let (Some(start), Some(end)) =
525                    (checked_ticks_at(from, rate), ticks_at_or_after(to, rate))
526                else {
527                    return;
528                };
529                if let Ok(range) =
530                    TimeRange::new(MediaTime::from_ticks(start), MediaTime::from_ticks(end))
531                {
532                    merge_need(needs, clip.source_id, range);
533                }
534            });
535        }
536        Ok(())
537    }
538
539    /// Rejects a block [`Self::render`] would refuse.
540    fn check_block(&self, start_sample: i64, frames: usize) -> Result<(), EngineError> {
541        if start_sample < 0 || frames == 0 || frames > self.max_block {
542            return Err(EngineError::InvalidAudioConfiguration);
543        }
544        // Every later position lies inside this span, so once it fits in ticks nothing below can
545        // overflow.
546        i64::try_from(frames)
547            .ok()
548            .and_then(|frames| start_sample.checked_add(frames))
549            .and_then(|end| end.checked_add(self.lookahead))
550            .and_then(|end| checked_ticks_at(end, self.sample_rate))
551            .map(|_| ())
552            .ok_or(EngineError::InvalidAudioConfiguration)
553    }
554
555    /// Finite state-settling history requested on discontinuities, in samples.
556    /// This is separate from latency; [`Self::audio_needs`] includes the
557    /// history each clip's stages replay. It is at most
558    /// [`MAX_SETTLING_SECONDS`] of samples.
559    #[must_use]
560    pub const fn required_history_samples(&self) -> i64 {
561        self.history
562    }
563
564    fn preroll_start(&self, start: i64) -> Result<i64, EngineError> {
565        if start < 0 {
566            return Err(EngineError::InvalidAudioConfiguration);
567        }
568        let history = if self.continues_at(start).is_some() {
569            0
570        } else {
571            self.history
572        };
573        // Before timeline zero every input is silent, so the history replay stops at zero; only
574        // the latency pre-roll reaches before it.
575        Ok(start
576            .saturating_sub(history)
577            .max(0)
578            .saturating_sub(self.lookahead))
579    }
580
581    /// How many samples the replay before a seek to `target` mixes in its
582    /// block at `position`: up to [`Self::max_block`], and never past the
583    /// sample at which a stage starts. A stage runs from the block it starts
584    /// in, so a block cut there starts it at exactly its own sample, and the
585    /// replay, and with it the mix, is the same for every block size.
586    fn replay_frames(&self, position: i64, target: i64) -> usize {
587        let room = i64::try_from(self.max_block).unwrap_or(i64::MAX);
588        let mut end = target.min(position.saturating_add(room));
589        // Longer leads start earlier, so the starts rise along the leads: the first one past
590        // `position` is the next cut.
591        let begun = self
592            .stage_leads
593            .partition_point(|&lead| Stages::start(target, lead, self.lookahead) <= position);
594        if let Some(&lead) = self.stage_leads.get(begun) {
595            end = end.min(Stages::start(target, lead, self.lookahead));
596        }
597        usize::try_from(end - position).unwrap_or(1)
598    }
599
600    /// How far `start` lies from where the last block ended when a block
601    /// there continues it: zero for the very next sample, or a step of at
602    /// most [`Self::max_block`] samples either way under
603    /// [`AudioContinuity::ContinueSmallGaps`]. `None` when it resets and
604    /// replays: before the first block, after a reset, or for a jump.
605    fn continues_at(&self, start: i64) -> Option<i64> {
606        let step = start.checked_sub(self.next_sample?)?;
607        let room = i64::try_from(self.max_block).unwrap_or(i64::MAX);
608        let joins = self.continuity == AudioContinuity::ContinueSmallGaps && step.abs() <= room;
609        (step == 0 || joins).then_some(step)
610    }
611
612    /// Moves where every built clip chain continues from by `step` samples,
613    /// as the block after a small step reads every input that much later (or
614    /// earlier), so a chain carries its state across the step rather than
615    /// starting fresh.
616    fn step_live_chains(&mut self, step: i64) {
617        if step == 0 {
618            return;
619        }
620        for &(bus, position) in &self.live_chains {
621            if let Some(clip) = self.buses[bus].clips[position].chain.as_mut() {
622                clip.next_input = clip.next_input.and_then(|next| next.checked_add(step));
623            }
624        }
625    }
626
627    /// Chooses what [`Self::render`] does with a block that does not start
628    /// where the previous one ended ([`AudioContinuity`]); new renderers are
629    /// [`AudioContinuity::Exact`]. Choosing another policy than the current
630    /// one resets the renderer ([`Self::reset`]), so no state mixed under one
631    /// carries into the other: an exact renderer mixes what a fresh one would
632    /// from its next block on.
633    pub fn set_continuity(&mut self, continuity: AudioContinuity) {
634        if self.continuity != continuity {
635            self.reset();
636            self.continuity = continuity;
637        }
638    }
639
640    /// The policy [`Self::set_continuity`] last chose.
641    #[must_use]
642    pub const fn continuity(&self) -> AudioContinuity {
643        self.continuity
644    }
645
646    /// Clears every processor history without changing authored parameters.
647    ///
648    /// [`Self::render`] does this itself whenever a block does not continue
649    /// the previous one. A host calls it only to start fresh at the very
650    /// position the last block ended, such as an export beginning where
651    /// preview stopped.
652    pub fn reset(&mut self) {
653        self.next_sample = None;
654        // A built chain stays built, so a seek among the clips that sound needs no new memory;
655        // the next block drops those it does not mix before it builds any.
656        for &(bus, position) in &self.live_chains {
657            if let Some(clip) = self.buses[bus].clips[position].chain.as_mut() {
658                if let Some(chain) = clip.chain.as_mut() {
659                    chain.reset();
660                }
661                clip.next_input = None;
662            }
663        }
664        for bus in &mut self.buses {
665            bus.chain.reset();
666            if let Some(ducking) = bus.ducker.as_mut() {
667                ducking.ducker.reset();
668            }
669        }
670        self.master_chain.reset();
671        if let Some(limiter) = self.limiter.as_mut() {
672            limiter.reset();
673        }
674    }
675
676    /// Mixes `frames` samples starting at an absolute project-rate sample index.
677    ///
678    /// A block that does not start where the previous one ended, a seek or an
679    /// export after preview, first resets every processor, so its output is
680    /// what a fresh renderer would produce and no reverb tail, compressor
681    /// envelope, or ducking gain carries over from elsewhere on the timeline.
682    /// That is the default policy, [`AudioContinuity::Exact`]; under
683    /// [`AudioContinuity::ContinueSmallGaps`], which live preview may choose
684    /// ([`Self::set_continuity`]), a block at most [`Self::max_block`] samples
685    /// from where the previous one ended continues instead, and only a longer
686    /// jump resets.
687    ///
688    /// After a reset it replays the history [`Self::audio_needs`] asks for in
689    /// bounded blocks, cut where each stage starts, so the mix after a seek
690    /// does not depend on the block size. Recursive effects use a finite
691    /// settling horizon (32 exponential time constants, with filter/feedback
692    /// margins, and no stage leads by more than [`MAX_SETTLING_SECONDS`]), not
693    /// a bit-exact promise for arbitrary infinite-history DSP. Each stage joins
694    /// the replay only when its own horizon, plus everything downstream of it,
695    /// begins: a long reverb on one track replays that track, not every track.
696    /// A block that continues the previous one resets and replays nothing.
697    ///
698    /// # Errors
699    /// Returns [`EngineError::InvalidAudioConfiguration`] for a negative start,
700    /// a block larger than [`Self::max_block`], or a block whose timeline span
701    /// does not fit in ticks, and an evaluation error when the snapshot cannot
702    /// be evaluated over the block's timeline span.
703    pub fn render(
704        &mut self,
705        snapshot: &EngineProject,
706        start_sample: i64,
707        frames: usize,
708        provider: &dyn AudioBlockProvider,
709        transport: TransportState,
710    ) -> Result<AudioRenderStatus, EngineError> {
711        self.check_block(start_sample, frames)?;
712        let end_sample = start_sample + i64::try_from(frames).unwrap_or(i64::MAX);
713        if self.hold_incomplete {
714            let missing_sources = self.unready_sources(snapshot, start_sample, frames, provider)?;
715            if !missing_sources.is_empty() {
716                for channel in 0..self.layout.channels() {
717                    self.output[channel][..frames].fill(0.0);
718                }
719                return Ok(AudioRenderStatus {
720                    start_sample,
721                    frames,
722                    missing_sources,
723                });
724            }
725        }
726        let mut missing_sources: Vec<SourceId> = Vec::new();
727        if let Some(step) = self.continues_at(start_sample) {
728            self.step_live_chains(step);
729        } else {
730            let mut position = self.preroll_start(start_sample)?;
731            self.reset();
732            // Rebuild from bounded source history: no state from another position survives.
733            while position < start_sample {
734                let count = self.replay_frames(position, start_sample);
735                self.mix_block(
736                    snapshot,
737                    position,
738                    count,
739                    provider,
740                    transport,
741                    Some(start_sample),
742                    &mut missing_sources,
743                )?;
744                position += i64::try_from(count).unwrap_or(i64::MAX);
745            }
746        }
747        self.mix_block(
748            snapshot,
749            start_sample,
750            frames,
751            provider,
752            transport,
753            None,
754            &mut missing_sources,
755        )?;
756        self.next_sample = Some(end_sample);
757
758        Ok(AudioRenderStatus {
759            start_sample,
760            frames,
761            missing_sources,
762        })
763    }
764
765    /// The sources a render of `[start_sample, start_sample + frames)` would
766    /// read, its pre-roll included, that `provider` does not supply. It asks
767    /// `provider` for every span the render reads and checks each as the
768    /// render would, but reads no sample ([`Fill::Presence`]), so a held
769    /// render costs a mix and a lookup, not two mixes' source reads. No
770    /// buffer, processor, position, or meter moves.
771    fn unready_sources(
772        &mut self,
773        snapshot: &EngineProject,
774        start_sample: i64,
775        frames: usize,
776        provider: &dyn AudioBlockProvider,
777    ) -> Result<Vec<SourceId>, EngineError> {
778        let mut missing = Vec::new();
779        let mut position = if self.continues_at(start_sample).is_some() {
780            start_sample
781        } else {
782            self.preroll_start(start_sample)?
783        };
784        while position < start_sample {
785            let count = self.replay_frames(position, start_sample);
786            self.read_block(
787                snapshot,
788                position,
789                count,
790                provider,
791                Some(start_sample),
792                &mut missing,
793            )?;
794            position += i64::try_from(count).unwrap_or(i64::MAX);
795        }
796        self.read_block(snapshot, start_sample, frames, provider, None, &mut missing)?;
797        Ok(missing)
798    }
799
800    /// Checks, as [`Self::mix_block`] reads them but reading and mixing
801    /// nothing, the spans each clip of a block needs, adding the sources
802    /// `provider` lacks to `missing`.
803    fn read_block(
804        &mut self,
805        snapshot: &EngineProject,
806        start: i64,
807        frames: usize,
808        provider: &dyn AudioBlockProvider,
809        replay_target: Option<i64>,
810        missing: &mut Vec<SourceId>,
811    ) -> Result<(), EngineError> {
812        let frames_i64 = i64::try_from(frames).map_err(|_| EngineError::OutOfRange)?;
813        let stages = Stages {
814            target: replay_target,
815            block_end: start + frames_i64,
816            lookahead: self.lookahead,
817        };
818        let span = self.block_span(start, frames_i64)?;
819        let mut scratch = std::mem::take(&mut self.span_scratch);
820        let described = match snapshot.evaluate_audio_in(span, &mut scratch) {
821            Ok(described) => described,
822            Err(error) => {
823                self.span_scratch = scratch;
824                return Err(EngineError::Evaluate(error));
825            }
826        };
827        let master_input = start + self.master_latency;
828        for clip in described.iter() {
829            let Some(read) = self.clip_read(&clip, master_input, frames, stages) else {
830                continue;
831            };
832            let window = read.window;
833            let complete = self.fill_clip(
834                &clip,
835                read.first,
836                window.count,
837                window.content,
838                provider,
839                Fill::Presence,
840            );
841            if !complete && !missing.contains(clip.source_id) {
842                missing.push(clip.source_id.clone());
843            }
844        }
845        self.span_scratch = scratch;
846        Ok(())
847    }
848
849    /// Mixes the block of output samples `[start, start + frames)` into
850    /// `output`, carrying every processor's state forward.
851    ///
852    /// Each stage reads ahead by the latency between it and the output: the
853    /// master chain's input is at `start + master_latency`, a bus chain's at
854    /// that plus its own latency, a clip chain's at that plus its own. What
855    /// each stage outputs is then aligned with the stage after it.
856    ///
857    /// A block replayed before a seek to `replay_target` runs only the stages
858    /// whose settling has begun; see [`Stages`].
859    #[allow(clippy::too_many_arguments)]
860    fn mix_block(
861        &mut self,
862        snapshot: &EngineProject,
863        start: i64,
864        frames: usize,
865        provider: &dyn AudioBlockProvider,
866        transport: TransportState,
867        replay_target: Option<i64>,
868        missing_sources: &mut Vec<SourceId>,
869    ) -> Result<(), EngineError> {
870        let channels = self.layout.channels();
871        let frames_i64 = i64::try_from(frames).map_err(|_| EngineError::OutOfRange)?;
872        let stages = Stages {
873            target: replay_target,
874            block_end: start + frames_i64,
875            lookahead: self.lookahead,
876        };
877        let span = self.block_span(start, frames_i64)?;
878        // The scratch leaves `self` while the span borrows it, so the clip loop below can still
879        // mix into `self`.
880        let mut scratch = std::mem::take(&mut self.span_scratch);
881        let described = match snapshot.evaluate_audio_in(span, &mut scratch) {
882            Ok(described) => described,
883            Err(error) => {
884                self.span_scratch = scratch;
885                return Err(EngineError::Evaluate(error));
886            }
887        };
888
889        for channel in 0..channels {
890            self.mix[channel][..frames].fill(0.0);
891        }
892        // Each bus clears its buffer when a stage first writes it (`TrackBus::make_live`), so a
893        // bus the block leaves idle costs nothing.
894        for bus in &mut self.buses {
895            bus.live = false;
896        }
897
898        let master_input = start + self.master_latency;
899        self.keep_mixed_chains(&described, master_input, frames, stages);
900        let mut complete_block = true;
901        for clip in described.iter() {
902            let clip = &clip;
903            let Some(read) = self.clip_read(clip, master_input, frames, stages) else {
904                continue;
905            };
906            let window = read.window;
907            let complete = self.fill_clip(
908                clip,
909                read.first,
910                window.count,
911                window.content,
912                provider,
913                Fill::Samples,
914            );
915            complete_block &= complete;
916            if !complete && !missing_sources.contains(clip.source_id) {
917                missing_sources.push(clip.source_id.clone());
918            }
919            if let Some(slot) = read.slot {
920                self.apply_clip_chain(slot, read.first, transport, window.count);
921            }
922            self.mix_clip_into_bus(
923                clip,
924                read.bus,
925                read.bus_input,
926                window.offset,
927                window.count,
928                frames,
929            );
930        }
931        self.span_scratch = scratch;
932
933        self.process_buses(master_input, transport, frames, stages);
934        self.apply_ducking(&self.ctx(master_input, transport), frames, stages);
935        if stages.run(self.master_lead) {
936            self.sum_buses(master_input, frames);
937            self.finish_master(
938                start,
939                master_input,
940                transport,
941                frames,
942                stages,
943                complete_block,
944            );
945        }
946        self.blocks_mixed += 1;
947        Ok(())
948    }
949
950    /// Drops the chain of every clip the block `described` does not mix,
951    /// before the block builds any, so the chains built at once are only
952    /// those of the clips one block mixes: never those of a position a seek
953    /// left with those of the one it reached, nor a clip's after its last
954    /// block with those of the next. Mixing a dropped clip again would not
955    /// continue from where it stopped, so it would start from fresh state: a
956    /// chain built then is the same.
957    fn keep_mixed_chains(
958        &mut self,
959        described: &alpha2_eval::AudioSpan<'_, '_>,
960        master_input: i64,
961        frames: usize,
962        stages: Stages,
963    ) {
964        let block = self.blocks_mixed;
965        for clip in described.iter() {
966            let Some((bus, position)) = self
967                .clip_read(&clip, master_input, frames, stages)
968                .and_then(|read| read.slot)
969            else {
970                continue;
971            };
972            if let Some(chain) = self.buses[bus].clips[position].chain.as_mut() {
973                chain.ran_in = block;
974            }
975        }
976        let buses = &mut self.buses;
977        self.live_chains.retain(|&(bus, position)| {
978            let Some(clip) = buses[bus].clips[position].chain.as_mut() else {
979                return false;
980            };
981            if clip.ran_in == block {
982                return true;
983            }
984            clip.chain = None;
985            clip.next_input = None;
986            false
987        });
988    }
989
990    fn ctx(&self, sample: i64, transport: TransportState) -> ProcessCtx {
991        ProcessCtx {
992            position: MediaTime::from_ticks(ticks_at(sample, self.sample_rate)),
993            sample_position: sample,
994            sample_rate: self.sample_rate,
995            transport,
996        }
997    }
998
999    /// Applies a static gain, in dB, after the master gain and before the
1000    /// limiter: loudness normalization. Zero removes it, and so does starting
1001    /// to meter ([`Self::set_loudness_metering`]). It takes any gain
1002    /// [`alpha2_audio::LoudnessNormalization::plan`] gives.
1003    ///
1004    /// # Errors
1005    /// Returns [`EngineError::InvalidNormalizationGain`] for a non-finite
1006    /// gain, or one whose linear factor a 32-bit float cannot hold as a
1007    /// finite, positive number (past about 760 dB either way).
1008    pub fn set_normalization_gain_db(&mut self, gain_db: f64) -> Result<(), EngineError> {
1009        #[allow(clippy::cast_possible_truncation)]
1010        let gain = libm::pow(10.0, gain_db / 20.0) as f32;
1011        if !gain_db.is_finite() || !gain.is_normal() {
1012            return Err(EngineError::InvalidNormalizationGain);
1013        }
1014        self.normalization_gain = gain;
1015        Ok(())
1016    }
1017
1018    /// Whether a block a source is missing from is held: mixed as silence
1019    /// with nothing advanced, so that rendering it again once the host has
1020    /// supplied the source continues exactly as an uninterrupted render
1021    /// would. Off (the default), such a block mixes the clips it has and
1022    /// moves on, as live playback wants; an export turns it on.
1023    pub const fn set_hold_incomplete_blocks(&mut self, hold: bool) {
1024        self.hold_incomplete = hold;
1025    }
1026
1027    /// Starts (or restarts, from empty) measuring the loudness of what
1028    /// reaches the limiter, over the blocks rendered from now on; `false`
1029    /// stops. The measurement is of the complete requested blocks only:
1030    /// never of the history the renderer replays after a seek, nor of a block
1031    /// a source was missing from, which the host renders again.
1032    ///
1033    /// Starting removes the normalization gain
1034    /// ([`Self::set_normalization_gain_db`]), so a measurement is of the mix
1035    /// as authored however many times one is planned from: a gain set after
1036    /// starting is measured, which checks a normalized mix.
1037    ///
1038    /// # Errors
1039    /// Returns [`EngineError::InvalidAudioConfiguration`] for a layout or
1040    /// rate the meter does not support.
1041    pub fn set_loudness_metering(&mut self, enabled: bool) -> Result<(), EngineError> {
1042        if enabled {
1043            self.normalization_gain = 1.0;
1044        }
1045        self.meter = if enabled {
1046            Some(
1047                LoudnessMeter::new(self.sample_rate, self.layout.channels())
1048                    .map_err(|_| EngineError::InvalidAudioConfiguration)?,
1049            )
1050        } else {
1051            None
1052        };
1053        Ok(())
1054    }
1055
1056    /// What the meter has measured, if metering is on.
1057    #[must_use]
1058    pub fn loudness_report(&self) -> Option<LoudnessReport> {
1059        self.meter.as_ref().map(LoudnessMeter::report)
1060    }
1061
1062    /// The linear threshold of the master limiter the meter reads in front
1063    /// of, or `None` for a project without one. A loudness plan takes it
1064    /// ([`alpha2_audio::LoudnessNormalization::plan`]) so the gain it plans
1065    /// stops short of the limiter, which would otherwise cut it back.
1066    #[must_use]
1067    pub fn limiter_threshold(&self) -> Option<f64> {
1068        self.master
1069            .limiter
1070            .map(|limiter| f64::from(limiter.threshold))
1071    }
1072
1073    /// Starts (or restarts, from empty) measuring the loudness of what the
1074    /// renderer outputs, after the limiter and the normalization gain, over
1075    /// the blocks rendered from now on; `false` stops. It measures the same
1076    /// blocks [`Self::set_loudness_metering`] does, and leaves the gain as it
1077    /// is: an export measures what it hands its encoder.
1078    ///
1079    /// # Errors
1080    /// Returns [`EngineError::InvalidAudioConfiguration`] for a layout or
1081    /// rate the meter does not support.
1082    pub fn set_output_loudness_metering(&mut self, enabled: bool) -> Result<(), EngineError> {
1083        self.output_meter = if enabled {
1084            Some(
1085                LoudnessMeter::new(self.sample_rate, self.layout.channels())
1086                    .map_err(|_| EngineError::InvalidAudioConfiguration)?,
1087            )
1088        } else {
1089            None
1090        };
1091        Ok(())
1092    }
1093
1094    /// What the output meter has measured, if output metering is on.
1095    #[must_use]
1096    pub fn output_loudness_report(&self) -> Option<LoudnessReport> {
1097        self.output_meter.as_ref().map(LoudnessMeter::report)
1098    }
1099
1100    /// Copies the most recent render into an owned block for offline sinks.
1101    #[must_use]
1102    pub fn block(&self, status: &AudioRenderStatus) -> AudioBlock {
1103        AudioBlock {
1104            start_sample: status.start_sample,
1105            sample_rate: self.sample_rate,
1106            speakers: match self.layout {
1107                ChannelLayout::Mono => SpeakerLayout::MONO,
1108                ChannelLayout::Stereo => SpeakerLayout::STEREO,
1109            },
1110            channels: (0..self.layout.channels())
1111                .map(|channel| self.output[channel][..status.frames].to_vec())
1112                .collect(),
1113        }
1114    }
1115
1116    /// The bus of a clip's track. Its track order is its bus index unless the
1117    /// clip comes from a snapshot other than this renderer's, which falls back
1118    /// to the track's ID.
1119    fn bus_of(&self, clip: &ActiveAudioClip) -> Option<usize> {
1120        match self.buses.get(clip.track_order) {
1121            Some(bus) if bus.id == *clip.track_id => Some(clip.track_order),
1122            _ => self.bus_of_track.get(clip.track_id).copied(),
1123        }
1124    }
1125
1126    /// A clip's slot, `(bus, position)`, found as [`Self::bus_of`] finds its bus.
1127    fn slot_of(&self, clip: &ActiveAudioClip) -> Option<(usize, usize)> {
1128        let slot = (clip.track_order, clip.clip_order);
1129        let matches = self
1130            .buses
1131            .get(slot.0)
1132            .and_then(|bus| bus.clips.get(slot.1))
1133            .is_some_and(|found| found.id == *clip.clip_id);
1134        if matches {
1135            Some(slot)
1136        } else {
1137            self.slot_of_clip.get(clip.clip_id).copied()
1138        }
1139    }
1140
1141    fn clip_chain(&self, (bus, position): (usize, usize)) -> Option<&ClipChain> {
1142        self.buses[bus].clips[position].chain.as_ref()
1143    }
1144
1145    /// Whether the bus at `index` stays out of the master sum: it is muted,
1146    /// or outside the solo.
1147    fn is_silent_bus(&self, index: usize) -> bool {
1148        self.buses[index].audio.is_silenced(self.any_solo)
1149    }
1150
1151    /// The timeline span a block of output samples `[start, start + frames)`
1152    /// evaluates: the block, read ahead by the graph's lookahead.
1153    fn block_span(&self, start: i64, frames: i64) -> Result<TimeRange, EngineError> {
1154        let invalid = EngineError::InvalidAudioConfiguration;
1155        let span_end = start
1156            .checked_add(frames)
1157            .and_then(|end| end.checked_add(self.lookahead))
1158            .ok_or(invalid.clone())?;
1159        let start_ticks = checked_ticks_at(start, self.sample_rate).ok_or(invalid.clone())?;
1160        let end_ticks = checked_ticks_at(span_end, self.sample_rate)
1161            .zip(start_ticks.checked_add(1))
1162            .map(|(end, after_start)| end.max(after_start))
1163            .ok_or(invalid.clone())?;
1164        TimeRange::new(
1165            MediaTime::from_ticks(start_ticks),
1166            MediaTime::from_ticks(end_ticks),
1167        )
1168        .map_err(|_| invalid)
1169    }
1170
1171    /// Where a block whose master input starts at `master_input` reads a
1172    /// clip, or `None` where it skips it: a clip that is not heard, on a bus
1173    /// that is not, whose stages have not begun settling in a replay, or
1174    /// outside the block. The mix and [`Self::audio_needs`] both decide here,
1175    /// so a host is asked for exactly what the mix reads.
1176    fn clip_read(
1177        &self,
1178        clip: &ActiveAudioClip,
1179        master_input: i64,
1180        frames: usize,
1181        stages: Stages,
1182    ) -> Option<ClipRead> {
1183        let bus = self.bus_of(clip)?;
1184        if !clip.audio.is_audible() || !self.buses[bus].read {
1185            return None;
1186        }
1187        let bus_input = master_input + self.buses[bus].latency;
1188        let slot = self.slot_of(clip);
1189        let chain = slot.and_then(|slot| self.clip_chain(slot));
1190        let lead = chain.map_or(self.buses[bus].chain_lead, |chain| chain.lead);
1191        if !stages.run(lead) {
1192            return None;
1193        }
1194        let clip_latency = chain.map_or(0, |chain| chain.latency);
1195        let chain_input = bus_input + clip_latency;
1196        let window = self.clip_window(clip, chain_input, frames, clip_latency)?;
1197        Some(ClipRead {
1198            bus,
1199            bus_input,
1200            slot,
1201            first: chain_input + i64::try_from(window.offset).ok()?,
1202            window,
1203        })
1204    }
1205
1206    /// The part of a block of chain input starting at `input` that a clip
1207    /// covers: the clip itself, then `latency` more samples of silence that
1208    /// flush its chain's delay line.
1209    fn clip_window(
1210        &self,
1211        clip: &ActiveAudioClip,
1212        input: i64,
1213        frames: usize,
1214        latency: i64,
1215    ) -> Option<ClipWindow> {
1216        let first = sample_at_or_after(clip.timeline_range.start().ticks(), self.sample_rate);
1217        let last = sample_at_or_after(clip.timeline_range.end().ticks(), self.sample_rate);
1218        let block_end = input.checked_add(i64::try_from(frames).ok()?)?;
1219        let begin = first.max(input);
1220        let end = last.saturating_add(latency).min(block_end);
1221        if end <= begin {
1222            return None;
1223        }
1224        Some(ClipWindow {
1225            offset: usize::try_from(begin - input).ok()?,
1226            count: usize::try_from(end - begin).ok()?,
1227            content: usize::try_from((last.min(end) - begin).max(0)).ok()?,
1228        })
1229    }
1230
1231    /// Reads and resamples one clip's source window into `clip_a`, or with
1232    /// [`Fill::Presence`] only checks that it could.
1233    ///
1234    /// Returns whether every requested sample came from supplied media.
1235    fn fill_clip(
1236        &mut self,
1237        clip: &ActiveAudioClip,
1238        first_sample: i64,
1239        count: usize,
1240        content: usize,
1241        provider: &dyn AudioBlockProvider,
1242        fill: Fill,
1243    ) -> bool {
1244        if fill == Fill::Samples {
1245            for channel in 0..self.layout.channels() {
1246                self.clip_a[channel][..count].fill(0.0);
1247            }
1248        }
1249        // Past the clip's end is the silence that flushes its chain.
1250        let count = content;
1251        if count == 0 {
1252            return true;
1253        }
1254        let rate = self.sample_rate;
1255        match clip.time_map {
1256            // A frozen clip is silent.
1257            TimeMap::Freeze { .. } => true,
1258            TimeMap::Forward | TimeMap::Reverse => {
1259                let reversed = clip.time_map == TimeMap::Reverse;
1260                SourceMap::stretched(clip, reversed, rate).is_some_and(|map| {
1261                    self.read_source(clip.source_id, &map, first_sample, 0, count, provider, fill)
1262                })
1263            }
1264            TimeMap::Loop { mode } => {
1265                self.fill_loop(clip, mode, first_sample, count, provider, fill)
1266            }
1267        }
1268    }
1269
1270    /// Reads a looping clip one pass at a time: within a pass its source
1271    /// position is linear. A repeating loop fades out before each wrap and in
1272    /// after it.
1273    fn fill_loop(
1274        &mut self,
1275        clip: &ActiveAudioClip,
1276        mode: LoopMode,
1277        first_sample: i64,
1278        count: usize,
1279        provider: &dyn AudioBlockProvider,
1280        fill: Fill,
1281    ) -> bool {
1282        let rate = self.sample_rate;
1283        let end = clip.timeline_range.end().ticks();
1284        #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
1285        let fade = ((f64::from(rate) * LOOP_FADE_SECONDS).round() as i64).max(1);
1286        let mut complete = true;
1287        let mapped = loop_pieces(clip, mode, first_sample, count, rate, |piece| {
1288            complete &= self.read_source(
1289                clip.source_id,
1290                &piece.map,
1291                piece.sample,
1292                piece.index,
1293                piece.length,
1294                provider,
1295                fill,
1296            );
1297            if mode == LoopMode::Repeat && fill == Fill::Samples {
1298                // Fade around wraps inside the clip only; its own ends have authored fades.
1299                let fade_in = (piece.pass > 0).then(|| sample_at_or_after(piece.pass_start, rate));
1300                let fade_out = (piece.pass_end < end).then_some(piece.piece_end);
1301                self.fade_wraps(
1302                    piece.sample,
1303                    piece.index,
1304                    piece.length,
1305                    fade_in,
1306                    fade_out,
1307                    fade,
1308                );
1309            }
1310        });
1311        mapped && complete
1312    }
1313
1314    /// Scales samples near a loop's wraps: the gain rises from the wrap at
1315    /// `fade_in` and falls toward the one at `fade_out`, linearly over `fade`
1316    /// samples, centred between the samples either side of the wrap.
1317    fn fade_wraps(
1318        &mut self,
1319        first_sample: i64,
1320        offset: usize,
1321        count: usize,
1322        fade_in: Option<i64>,
1323        fade_out: Option<i64>,
1324        fade: i64,
1325    ) {
1326        #[allow(clippy::cast_precision_loss)]
1327        let fade = fade as f64;
1328        for index in 0..count {
1329            let sample = first_sample + i64::try_from(index).unwrap_or(i64::MAX);
1330            #[allow(clippy::cast_precision_loss)]
1331            let after = fade_in.map_or(f64::INFINITY, |wrap| (sample - wrap) as f64 + 0.5);
1332            #[allow(clippy::cast_precision_loss)]
1333            let before = fade_out.map_or(f64::INFINITY, |wrap| (wrap - sample) as f64 - 0.5);
1334            let distance = after.min(before);
1335            if distance >= fade {
1336                continue;
1337            }
1338            #[allow(clippy::cast_possible_truncation)]
1339            let gain = (distance / fade).clamp(0.0, 1.0) as f32;
1340            for channel in 0..self.layout.channels() {
1341                self.clip_a[channel][offset + index] *= gain;
1342            }
1343        }
1344    }
1345
1346    /// Reads `count` project samples from `first_sample` through `map` into
1347    /// `clip_a` at `offset`. With [`Fill::Presence`] it stops once it has
1348    /// checked the span `provider` returns, before reading any sample.
1349    ///
1350    /// Returns whether every requested sample came from supplied media.
1351    #[allow(clippy::too_many_arguments)]
1352    fn read_source(
1353        &mut self,
1354        source_id: &SourceId,
1355        map: &SourceMap,
1356        first_sample: i64,
1357        offset: usize,
1358        count: usize,
1359        provider: &dyn AudioBlockProvider,
1360        fill: Fill,
1361    ) -> bool {
1362        // Positions are exact rationals in samples, so a clip whose source lines up with the
1363        // project's sample grid copies samples unchanged.
1364        let Some((request_start, request_end)) = map.request(first_sample, count) else {
1365            return false;
1366        };
1367        let request_frames =
1368            usize::try_from(request_end.saturating_sub(request_start)).unwrap_or(0);
1369        let Some(span) = provider.block(source_id, request_start, request_frames) else {
1370            return false;
1371        };
1372        // A span that does not account for part of the window is media that has not arrived, not
1373        // silence.
1374        if !span.covers(request_start, request_end) {
1375            return false;
1376        }
1377        let source = span.block;
1378        if source.sample_rate != self.sample_rate {
1379            return false;
1380        }
1381
1382        // The source keeps its own channels; the fold into the project's is the engine's, so it is
1383        // the same on every platform.
1384        if source.speakers.channels() != source.channels.len() {
1385            return false;
1386        }
1387        let Some(downmix) = Downmix::new(source.speakers.speakers(), self.layout) else {
1388            return false;
1389        };
1390        if fill == Fill::Presence {
1391            return true;
1392        }
1393        #[cfg(test)]
1394        {
1395            self.samples_read += count;
1396        }
1397        let read = SourceRead {
1398            planes: source.channels.as_slice(),
1399            downmix: &downmix,
1400            start: source.start_sample,
1401            offset,
1402            count,
1403        };
1404        match (map.cursor(first_sample), map.interpolator()) {
1405            // A retime whose divisor does not fit the cursor: divide per sample.
1406            (None, interpolator) => self.read_divided(&read, map, first_sample, interpolator),
1407            (Some(cursor), None) => self.read_copied(&read, cursor),
1408            (Some(cursor), Some(interpolator)) => {
1409                self.read_filtered(&read, map, cursor, interpolator);
1410            }
1411        }
1412        true
1413    }
1414
1415    /// [`Self::read_source`] where every position lands on a source sample,
1416    /// one apart: a unity clip copies its source.
1417    fn read_copied(&mut self, read: &SourceRead<'_>, cursor: RationalCursor) {
1418        let Some(first) = cursor.whole.checked_sub(read.start) else {
1419            return;
1420        };
1421        for channel in 0..self.layout.channels() {
1422            let mix = read.downmix.channel(channel);
1423            let mut at = first;
1424            let outputs = &mut self.clip_a[channel][read.offset..read.offset + read.count];
1425            if let Some(plane) = mix.unity() {
1426                let plane = read.planes[plane].as_slice();
1427                for output in outputs {
1428                    *output = copied(plane, at);
1429                    at = at.wrapping_add(cursor.step_whole);
1430                }
1431            } else {
1432                for output in outputs {
1433                    *output = mixed_sample(mix, read.planes, at);
1434                    at = at.wrapping_add(cursor.step_whole);
1435                }
1436            }
1437        }
1438    }
1439
1440    /// [`Self::read_source`] through `interpolator`, one position after
1441    /// another from `cursor`. One set of weights per position serves every
1442    /// channel and plane, and a speed whose positions repeat their fractions
1443    /// after a few samples (2, 1.5, 1.25: any small ratio) weighs each
1444    /// fraction once per read; the weights are the same either way.
1445    fn read_filtered(
1446        &mut self,
1447        read: &SourceRead<'_>,
1448        map: &SourceMap,
1449        mut cursor: RationalCursor,
1450        interpolator: Interpolator,
1451    ) {
1452        let mut weights = std::mem::take(&mut self.weights);
1453        let reach = interpolator.half_taps() - 1;
1454        let taps = usize::try_from(2 * interpolator.half_taps()).unwrap_or(0);
1455        let phases = usize::try_from(cursor.period())
1456            .ok()
1457            .filter(|&period| period <= read.count / 2 && period * taps <= MAX_CACHED_WEIGHTS);
1458        if let Some(phases) = phases {
1459            weights.resize(phases * taps, 0.0);
1460            let mut scratch = std::mem::take(&mut self.phase_weights);
1461            let mut walk = cursor;
1462            for row in weights.chunks_exact_mut(taps) {
1463                interpolator.weights(map.fraction(&walk).1, &mut scratch);
1464                row.copy_from_slice(&scratch);
1465                walk.advance();
1466            }
1467            self.phase_weights = scratch;
1468        }
1469        let mut phase = 0;
1470        for index in 0..read.count {
1471            let (whole, fraction) = map.fraction(&cursor);
1472            cursor.advance();
1473            let row = if let Some(phases) = phases {
1474                let row = phase;
1475                phase = if phase + 1 == phases { 0 } else { phase + 1 };
1476                &weights[row * taps..(row + 1) * taps]
1477            } else {
1478                interpolator.weights(fraction, &mut weights);
1479                &weights[..]
1480            };
1481            let Some(first) = whole
1482                .checked_sub(read.start)
1483                .and_then(|at| at.checked_sub(reach))
1484            else {
1485                continue;
1486            };
1487            for channel in 0..self.layout.channels() {
1488                self.clip_a[channel][read.offset + index] =
1489                    filtered_sample(read.downmix.channel(channel), read.planes, first, row);
1490            }
1491        }
1492        self.weights = weights;
1493    }
1494
1495    /// [`Self::read_source`] for a map whose divisor does not fit a cursor,
1496    /// dividing at every sample.
1497    fn read_divided(
1498        &mut self,
1499        read: &SourceRead<'_>,
1500        map: &SourceMap,
1501        first_sample: i64,
1502        interpolator: Option<Interpolator>,
1503    ) {
1504        let mut weights = std::mem::take(&mut self.weights);
1505        for index in 0..read.count {
1506            let sample = first_sample + i64::try_from(index).unwrap_or(0);
1507            let Some((whole, fraction)) = map.position(sample) else {
1508                continue;
1509            };
1510            let Some(at) = whole.checked_sub(read.start) else {
1511                continue;
1512            };
1513            if let Some(interpolator) = interpolator {
1514                interpolator.weights(fraction, &mut weights);
1515            }
1516            for channel in 0..self.layout.channels() {
1517                let mix = read.downmix.channel(channel);
1518                self.clip_a[channel][read.offset + index] = match interpolator {
1519                    None => mixed_sample(mix, read.planes, at),
1520                    Some(interpolator) => filtered_sample(
1521                        mix,
1522                        read.planes,
1523                        at - interpolator.half_taps() + 1,
1524                        &weights,
1525                    ),
1526                };
1527            }
1528        }
1529        self.weights = weights;
1530    }
1531
1532    /// Runs a clip's chain over `count` samples of input starting at `input`.
1533    ///
1534    /// A chain that does not continue from where it stopped, such as a clip
1535    /// starting again after a seek, starts from fresh state rather than play
1536    /// out the tail it held.
1537    fn apply_clip_chain(
1538        &mut self,
1539        (bus, position): (usize, usize),
1540        input: i64,
1541        transport: TransportState,
1542        count: usize,
1543    ) {
1544        let ctx = self.ctx(input, transport);
1545        let (layout, sample_rate, max_block) = (self.layout, self.sample_rate, self.max_block);
1546        let Some(clip_chain) = self.buses[bus].clips[position].chain.as_mut() else {
1547            return;
1548        };
1549        if clip_chain.chain.is_none() {
1550            self.live_chains.push((bus, position));
1551        }
1552        let effects = &clip_chain.effects;
1553        let chain = clip_chain
1554            .chain
1555            .get_or_insert_with(|| clip_processors(effects, layout, sample_rate, max_block));
1556        clip_chain.ran_in = self.blocks_mixed;
1557        if clip_chain.next_input != Some(input) {
1558            chain.reset();
1559        }
1560        clip_chain.next_input = Some(input + i64::try_from(count).unwrap_or(0));
1561        chain.process_in_place(&ctx, &mut self.clip_a, &mut self.clip_b, count);
1562        #[cfg(test)]
1563        self.note_held();
1564    }
1565
1566    /// Records the clip effects built now in [`Self::most_held`].
1567    #[cfg(test)]
1568    fn note_held(&mut self) {
1569        let held = self
1570            .live_chains
1571            .iter()
1572            .filter_map(|&(bus, position)| self.buses[bus].clips[position].chain.as_ref())
1573            .filter(|clip| clip.chain.is_some())
1574            .fold((0, 0), |(effects, delay), clip| {
1575                (
1576                    effects + clip.effects.len(),
1577                    delay
1578                        + clip
1579                            .effects
1580                            .iter()
1581                            .map(AudioEffect::delay_ticks)
1582                            .sum::<u64>(),
1583                )
1584            });
1585        self.most_held = (self.most_held.0.max(held.0), self.most_held.1.max(held.1));
1586    }
1587
1588    /// Applies gain, fades, and panning, then sums into the bus, whose block
1589    /// is `frames` samples long.
1590    ///
1591    /// Gain and pan that stay constant over the window, with no fade reaching
1592    /// into it, are evaluated once; the product per sample is the same.
1593    /// Otherwise both follow the clip's envelope ([`fill_envelope`]).
1594    fn mix_clip_into_bus(
1595        &mut self,
1596        clip: &ActiveAudioClip,
1597        bus_index: usize,
1598        start_sample: i64,
1599        offset: usize,
1600        count: usize,
1601        frames: usize,
1602    ) {
1603        let channels = self.layout.channels();
1604        let sample_rate = self.sample_rate;
1605        let bus = &mut self.buses[bus_index];
1606        let first = start_sample + i64::try_from(offset).unwrap_or(0);
1607        bus.make_live(channels, frames);
1608        let gain = constant_value(&clip.audio.gain).map(|gain| gain.clamp(0.0, MAX_GAIN));
1609        let weights =
1610            constant_value(&clip.audio.pan).map(|pan| pan_gains(pan.clamp(-1.0, 1.0), channels));
1611        if let (Some(gain), Some(weights), false) =
1612            (gain, weights, fades_reach(clip, first, count, sample_rate))
1613        {
1614            for (channel, weight) in weights.iter().enumerate().take(channels) {
1615                let input = &self.clip_a[channel][..count];
1616                let output = &mut bus.output[channel][offset..offset + count];
1617                for (output, sample) in output.iter_mut().zip(input) {
1618                    *output += sample * gain * weight;
1619                }
1620            }
1621            return;
1622        }
1623        let audio = clip.audio;
1624        fill_envelope(
1625            &mut self.envelope[..],
1626            first,
1627            count,
1628            sample_rate,
1629            |ticks| {
1630                earliest(
1631                    earliest(
1632                        next_keyframe(&audio.gain, ticks),
1633                        next_keyframe(&audio.pan, ticks),
1634                    ),
1635                    next_fade_edge(clip, ticks),
1636                )
1637            },
1638            |time| {
1639                let gain =
1640                    sample_scalar(&audio.gain, time).clamp(0.0, MAX_GAIN) * fade_gain(clip, time);
1641                let [left, right] = pan_gains(sample_scalar(&audio.pan, time), channels);
1642                [gain, left, right]
1643            },
1644        );
1645        let [gains, left, right] = &self.envelope;
1646        for (channel, weights) in [left, right].into_iter().enumerate().take(channels) {
1647            let input = &self.clip_a[channel][..count];
1648            let output = &mut bus.output[channel][offset..offset + count];
1649            for (((output, sample), gain), weight) in
1650                output.iter_mut().zip(input).zip(gains).zip(weights)
1651            {
1652                *output += sample * gain * weight;
1653            }
1654        }
1655    }
1656
1657    fn process_buses(
1658        &mut self,
1659        master_input: i64,
1660        transport: TransportState,
1661        frames: usize,
1662        stages: Stages,
1663    ) {
1664        let channels = self.layout.channels();
1665        let sample_rate = self.sample_rate;
1666        for bus in &mut self.buses {
1667            if !stages.run(bus.chain_lead) || bus.chain.passes_through() {
1668                continue;
1669            }
1670            let input = master_input + bus.latency;
1671            let ctx = &ProcessCtx {
1672                position: MediaTime::from_ticks(ticks_at(input, sample_rate)),
1673                sample_position: input,
1674                sample_rate,
1675                transport,
1676            };
1677            bus.make_live(channels, frames);
1678            for channel in 0..channels {
1679                self.bus_in[channel][..frames].copy_from_slice(&bus.output[channel][..frames]);
1680            }
1681            match channels {
1682                1 => bus.chain.process(
1683                    ctx,
1684                    &[&self.bus_in[0][..frames]],
1685                    &mut [&mut bus.output[0][..frames]],
1686                ),
1687                2 => {
1688                    let (left, right) = bus.output.split_at_mut(1);
1689                    bus.chain.process(
1690                        ctx,
1691                        &[&self.bus_in[0][..frames], &self.bus_in[1][..frames]],
1692                        &mut [&mut left[0][..frames], &mut right[0][..frames]],
1693                    );
1694                }
1695                _ => {}
1696            }
1697        }
1698    }
1699
1700    /// Attenuates ducked buses from the pre-duck output of their sidechain.
1701    ///
1702    /// Every ducker writes into its own buffer before any bus's `output`
1703    /// changes, so a sidechain that is itself ducked is read before its
1704    /// ducking whatever the track order.
1705    fn apply_ducking(&mut self, ctx: &ProcessCtx, frames: usize, stages: Stages) {
1706        let channels = self.layout.channels();
1707        let ducks = |bus: &TrackBus, index: usize| {
1708            bus.active_sidechain(index).is_some() && stages.run(bus.ducker_lead)
1709        };
1710        for index in 0..self.buses.len() {
1711            if !ducks(&self.buses[index], index) {
1712                continue;
1713            }
1714            let Some(sidechain) = self.buses[index].active_sidechain(index) else {
1715                continue;
1716            };
1717            // Either bus may be idle this block: the ducker still runs, on silence.
1718            self.buses[sidechain].make_live(channels, frames);
1719            for channel in 0..channels {
1720                self.bus_in[channel][..frames]
1721                    .copy_from_slice(&self.buses[sidechain].output[channel][..frames]);
1722            }
1723            let bus = &mut self.buses[index];
1724            bus.make_live(channels, frames);
1725            let Some(BusDucker { ducker, output }) = bus.ducker.as_mut() else {
1726                continue;
1727            };
1728            match channels {
1729                1 => ducker.process(
1730                    ctx,
1731                    &[&bus.output[0][..frames], &self.bus_in[0][..frames]],
1732                    &mut [&mut output[0][..frames]],
1733                ),
1734                2 => {
1735                    let (left, right) = output.split_at_mut(1);
1736                    ducker.process(
1737                        ctx,
1738                        &[
1739                            &bus.output[0][..frames],
1740                            &bus.output[1][..frames],
1741                            &self.bus_in[0][..frames],
1742                            &self.bus_in[1][..frames],
1743                        ],
1744                        &mut [&mut left[0][..frames], &mut right[0][..frames]],
1745                    );
1746                }
1747                _ => {}
1748            }
1749        }
1750        for (index, bus) in self.buses.iter_mut().enumerate() {
1751            if ducks(bus, index)
1752                && let Some(ducking) = bus.ducker.as_mut()
1753            {
1754                std::mem::swap(&mut bus.output, &mut ducking.output);
1755            }
1756        }
1757    }
1758
1759    fn sum_buses(&mut self, start_sample: i64, frames: usize) {
1760        let channels = self.layout.channels();
1761        for bus_index in 0..self.buses.len() {
1762            // An idle bus adds silence: `+0.0` to a sum that is never `-0.0`, which leaves it as it
1763            // is.
1764            if self.is_silent_bus(bus_index) || !self.buses[bus_index].live {
1765                continue;
1766            }
1767            let bus = &self.buses[bus_index];
1768            #[cfg(test)]
1769            BUS_PLANES_SUMMED.set(BUS_PLANES_SUMMED.get() + channels);
1770            let gain = constant_value(&bus.audio.gain).map(|gain| gain.clamp(0.0, MAX_GAIN));
1771            let weights =
1772                constant_value(&bus.audio.pan).map(|pan| pan_gains(pan.clamp(-1.0, 1.0), channels));
1773            if let (Some(gain), Some(weights)) = (gain, weights) {
1774                for (channel, weight) in weights.iter().enumerate().take(channels) {
1775                    let input = &bus.output[channel][..frames];
1776                    let output = &mut self.mix[channel][..frames];
1777                    for (output, sample) in output.iter_mut().zip(input) {
1778                        *output += sample * gain * weight;
1779                    }
1780                }
1781                continue;
1782            }
1783            let audio = &bus.audio;
1784            fill_envelope(
1785                &mut self.envelope[..],
1786                start_sample,
1787                frames,
1788                self.sample_rate,
1789                |ticks| {
1790                    earliest(
1791                        next_keyframe(&audio.gain, ticks),
1792                        next_keyframe(&audio.pan, ticks),
1793                    )
1794                },
1795                |time| {
1796                    let gain = sample_scalar(&audio.gain, time).clamp(0.0, MAX_GAIN);
1797                    let [left, right] = pan_gains(sample_scalar(&audio.pan, time), channels);
1798                    [gain, left, right]
1799                },
1800            );
1801            let [gains, left, right] = &self.envelope;
1802            for (channel, weights) in [left, right].into_iter().enumerate().take(channels) {
1803                let input = &bus.output[channel][..frames];
1804                let output = &mut self.mix[channel][..frames];
1805                for (((output, sample), gain), weight) in
1806                    output.iter_mut().zip(input).zip(gains).zip(weights)
1807                {
1808                    *output += sample * gain * weight;
1809                }
1810            }
1811        }
1812    }
1813
1814    fn finish_master(
1815        &mut self,
1816        start_sample: i64,
1817        master_input: i64,
1818        transport: TransportState,
1819        frames: usize,
1820        stages: Stages,
1821        complete: bool,
1822    ) {
1823        let channels = self.layout.channels();
1824        let input_ctx = self.ctx(master_input, transport);
1825        let ctx = &self.ctx(start_sample, transport);
1826        match channels {
1827            1 => self.master_chain.process(
1828                &input_ctx,
1829                &[&self.mix[0][..frames]],
1830                &mut [&mut self.output[0][..frames]],
1831            ),
1832            2 => {
1833                let (left, right) = self.output.split_at_mut(1);
1834                self.master_chain.process(
1835                    &input_ctx,
1836                    &[&self.mix[0][..frames], &self.mix[1][..frames]],
1837                    &mut [&mut left[0][..frames], &mut right[0][..frames]],
1838                );
1839            }
1840            _ => {}
1841        }
1842        if let Some(gain) = constant_value(&self.master.gain) {
1843            let gain = gain.clamp(0.0, MAX_GAIN);
1844            for channel in 0..channels {
1845                for sample in &mut self.output[channel][..frames] {
1846                    *sample *= gain;
1847                }
1848            }
1849        } else {
1850            let gain = &self.master.gain;
1851            fill_envelope(
1852                &mut self.envelope[..],
1853                start_sample,
1854                frames,
1855                self.sample_rate,
1856                |ticks| next_keyframe(gain, ticks),
1857                |time| [sample_scalar(gain, time).clamp(0.0, MAX_GAIN)],
1858            );
1859            for channel in 0..channels {
1860                let envelope = &self.envelope[0][..frames];
1861                for (sample, gain) in self.output[channel][..frames].iter_mut().zip(envelope) {
1862                    *sample *= gain;
1863                }
1864            }
1865        }
1866        if self.normalization_gain.to_bits() != 1.0_f32.to_bits() {
1867            for channel in 0..channels {
1868                for sample in &mut self.output[channel][..frames] {
1869                    *sample *= self.normalization_gain;
1870                }
1871            }
1872        }
1873        // Only a complete requested block is measured: never the history replayed before it after
1874        // a seek, nor a block mixed without a source, which the host renders again.
1875        let measured = stages.target.is_none() && complete;
1876        if measured && let Some(meter) = self.meter.as_mut() {
1877            let planes = [&self.output[0][..frames], &self.output[1][..frames]];
1878            // The meter was made for this layout, so the block always fits.
1879            let _ = meter.push(&planes[..channels]);
1880        }
1881        if let Some(limiter) = self
1882            .limiter
1883            .as_mut()
1884            .filter(|_| stages.run(self.limiter_lead))
1885        {
1886            for channel in 0..channels {
1887                self.mix[channel][..frames].copy_from_slice(&self.output[channel][..frames]);
1888            }
1889            match channels {
1890                1 => limiter.process(
1891                    ctx,
1892                    &[&self.mix[0][..frames]],
1893                    &mut [&mut self.output[0][..frames]],
1894                ),
1895                2 => {
1896                    let (left, right) = self.output.split_at_mut(1);
1897                    limiter.process(
1898                        ctx,
1899                        &[&self.mix[0][..frames], &self.mix[1][..frames]],
1900                        &mut [&mut left[0][..frames], &mut right[0][..frames]],
1901                    );
1902                }
1903                _ => {}
1904            }
1905        }
1906        if measured && let Some(meter) = self.output_meter.as_mut() {
1907            let planes = [&self.output[0][..frames], &self.output[1][..frames]];
1908            let _ = meter.push(&planes[..channels]);
1909        }
1910    }
1911}
1912
1913/// One bus per track, in project order, and the buses by track ID.
1914fn track_buses(
1915    project: &alpha2_model::Project,
1916    layout: ChannelLayout,
1917    sample_rate: u32,
1918    max_block: usize,
1919) -> (Vec<TrackBus>, HashMap<TrackId, usize>) {
1920    let mut buses = Vec::new();
1921    let mut bus_of_track = HashMap::new();
1922    // Every track gets a bus: a clip on a visual track contributes sound when its source
1923    // declares an audio stream.
1924    for track in &project.timeline.tracks {
1925        let mut chain = ProcessorChain::new(layout);
1926        push_effects(&mut chain, &track.audio.effects);
1927        chain.prepare(sample_rate, max_block);
1928        let chain_latency = chain.latency_samples();
1929        let ducker = track.audio.ducking.as_ref().map(|ducking| {
1930            let mut ducker = Ducker::new(
1931                ducking.threshold,
1932                ducking.depth_db,
1933                ducking.attack_ms,
1934                ducking.release_ms,
1935            );
1936            ducker.prepare(sample_rate, max_block);
1937            BusDucker {
1938                ducker,
1939                output: buffers(max_block),
1940            }
1941        });
1942        bus_of_track.insert(track.id.clone(), buses.len());
1943        buses.push(TrackBus {
1944            id: track.id.clone(),
1945            audio: track.audio.clone(),
1946            chain,
1947            ducker,
1948            sidechain: None,
1949            output: buffers(max_block),
1950            live: false,
1951            latency: i64::from(chain_latency),
1952            clips: clip_slots(&track.clips, layout, sample_rate, max_block),
1953            chain_lead: 0,
1954            ducker_lead: 0,
1955            read: false,
1956        });
1957    }
1958    (buses, bus_of_track)
1959}
1960
1961/// One slot per clip of a track, with a chain for each clip that has effects.
1962/// The chains are measured here and built when their clip is mixed.
1963fn clip_slots(
1964    clips: &[alpha2_model::Clip],
1965    layout: ChannelLayout,
1966    sample_rate: u32,
1967    max_block: usize,
1968) -> Vec<ClipSlot> {
1969    clips
1970        .iter()
1971        .map(|clip| ClipSlot {
1972            id: clip.id.clone(),
1973            chain: (!clip.audio.effects.is_empty()).then(|| {
1974                // Built once to measure, the same way it is built to mix, then dropped.
1975                let chain = clip_processors(&clip.audio.effects, layout, sample_rate, max_block);
1976                ClipChain {
1977                    effects: clip.audio.effects.clone(),
1978                    chain: None,
1979                    ran_in: 0,
1980                    latency: i64::from(chain.latency_samples()),
1981                    history: chain.history_samples(),
1982                    next_input: None,
1983                    lead: 0,
1984                }
1985            }),
1986        })
1987        .collect()
1988}
1989
1990/// A clip's chain, prepared to run in the renderer's clip buffers.
1991fn clip_processors(
1992    effects: &[AudioEffect],
1993    layout: ChannelLayout,
1994    sample_rate: u32,
1995    max_block: usize,
1996) -> ProcessorChain {
1997    let mut chain = ProcessorChain::new(layout);
1998    push_effects(&mut chain, effects);
1999    chain.prepare_nodes(sample_rate, max_block);
2000    chain
2001}
2002
2003/// The longest latency from a clip's input to its bus's output, over every
2004/// clip on every track.
2005fn longest_path_latency(buses: &[TrackBus]) -> i64 {
2006    buses
2007        .iter()
2008        .map(|bus| {
2009            bus.latency
2010                + bus
2011                    .clips
2012                    .iter()
2013                    .filter_map(|slot| slot.chain.as_ref())
2014                    .map(|chain| chain.latency)
2015                    .max()
2016                    .unwrap_or(0)
2017        })
2018        .max()
2019        .unwrap_or(0)
2020}
2021
2022/// Gives every bus, ducker, and clip chain its lead, and returns the longest.
2023///
2024/// A stage's lead is how long before a seek target it must start running for
2025/// its state to have settled there: its own history plus the lead of the
2026/// stage that consumes its output, which needs that output settled from its
2027/// own start. `master_lead` is the master chain's, whose input is the sum of
2028/// the buses. A bus feeds the sum, through its ducker when it has one, and
2029/// the ducker of every bus it sidechains; it serves the earliest of them.
2030fn assign_leads(buses: &mut [TrackBus], master_lead: i64, settling: Settling) -> i64 {
2031    let ducker_leads: Vec<Option<i64>> = buses
2032        .iter()
2033        .enumerate()
2034        .map(|(index, bus)| {
2035            let ducking = bus.ducker.as_ref()?;
2036            bus.active_sidechain(index)?;
2037            Some(settling.lead(master_lead, ducking.ducker.history_samples()))
2038        })
2039        .collect();
2040    let mut longest = master_lead;
2041    for index in 0..buses.len() {
2042        let sidechained = buses
2043            .iter()
2044            .enumerate()
2045            .filter(|&(user, bus)| bus.active_sidechain(user) == Some(index))
2046            .filter_map(|(user, _)| ducker_leads[user]);
2047        let settled_from = sidechained
2048            .chain(ducker_leads[index])
2049            .fold(master_lead, i64::max);
2050        let bus = &mut buses[index];
2051        bus.ducker_lead = ducker_leads[index].unwrap_or(0);
2052        bus.chain_lead = settling.lead(settled_from, bus.chain.history_samples());
2053        longest = longest.max(bus.chain_lead);
2054        for clip in bus.clips.iter_mut().filter_map(|slot| slot.chain.as_mut()) {
2055            clip.lead = settling.lead(bus.chain_lead, clip.history);
2056            longest = longest.max(clip.lead);
2057        }
2058    }
2059    longest
2060}
2061
2062/// The longest lead a stage takes, in samples: [`MAX_SETTLING_SECONDS`].
2063#[derive(Clone, Copy)]
2064struct Settling(i64);
2065
2066impl Settling {
2067    /// The lead of a stage with `history` of its own feeding one whose lead
2068    /// is `downstream`: never shorter than the stage it feeds, which must
2069    /// not start before it, and never longer than the cap.
2070    fn lead(self, downstream: i64, history: u32) -> i64 {
2071        downstream
2072            .saturating_add(i64::from(history))
2073            .min(self.0.max(downstream))
2074    }
2075}
2076
2077/// Which stages a block runs: all of them, except in a block replayed before
2078/// a seek to `target`, where a stage runs once its lead has begun.
2079#[derive(Clone, Copy)]
2080struct Stages {
2081    target: Option<i64>,
2082    block_end: i64,
2083    lookahead: i64,
2084}
2085
2086impl Stages {
2087    fn run(self, lead: i64) -> bool {
2088        self.target
2089            .is_none_or(|target| self.block_end > Self::start(target, lead, self.lookahead))
2090    }
2091
2092    /// The output sample from which a stage with `lead` runs in the replay
2093    /// before a seek to `target`. As in `preroll_start`: history stops at
2094    /// timeline zero, and every stage also fills the graph's delay lines.
2095    fn start(target: i64, lead: i64, lookahead: i64) -> i64 {
2096        (target - lead).max(0).saturating_sub(lookahead)
2097    }
2098}
2099
2100/// Every stage's lead, longest first and each once: the master chain's and
2101/// the limiter's, every bus chain's and running ducker's, and every clip
2102/// chain's.
2103fn stage_leads(buses: &[TrackBus], master_lead: i64, limiter_lead: i64) -> Vec<i64> {
2104    let mut leads = vec![master_lead, limiter_lead];
2105    for (index, bus) in buses.iter().enumerate() {
2106        leads.push(bus.chain_lead);
2107        if bus.active_sidechain(index).is_some() {
2108            leads.push(bus.ducker_lead);
2109        }
2110        leads.extend(
2111            bus.clips
2112                .iter()
2113                .filter_map(|slot| slot.chain.as_ref())
2114                .map(|chain| chain.lead),
2115        );
2116    }
2117    leads.sort_unstable_by(|a, b| b.cmp(a));
2118    leads.dedup();
2119    leads
2120}
2121
2122/// Marks the buses whose clips the mix reads: every bus the master sum
2123/// hears, and every bus that keys the ducker of one it hears. Solo chooses
2124/// what is heard, not what a heard track sounds like, so a ducked track sounds
2125/// the same soloed as in the whole mix; a muted track is not there at all and
2126/// keys nothing. A key is the pre-duck output of its bus, so the buses that
2127/// duck a key need not be read for it.
2128fn read_buses(buses: &mut [TrackBus], any_solo: bool) {
2129    let heard: Vec<bool> = buses
2130        .iter()
2131        .map(|bus| !bus.audio.is_silenced(any_solo))
2132        .collect();
2133    let keys: Vec<Option<usize>> = buses
2134        .iter()
2135        .enumerate()
2136        .map(|(index, bus)| heard[index].then(|| bus.active_sidechain(index)).flatten())
2137        .collect();
2138    for (index, bus) in buses.iter_mut().enumerate() {
2139        bus.read = heard[index] || (!bus.audio.muted && keys.contains(&Some(index)));
2140    }
2141}
2142
2143/// Points each ducked bus at its sidechain's bus, once every bus index
2144/// exists.
2145fn resolve_sidechains(buses: &mut [TrackBus], bus_of_track: &HashMap<TrackId, usize>) {
2146    for bus in buses {
2147        bus.sidechain = bus
2148            .audio
2149            .ducking
2150            .as_ref()
2151            .and_then(|ducking| bus_of_track.get(&ducking.sidechain_track).copied());
2152    }
2153}
2154
2155fn buffers(max_block: usize) -> [Vec<f32>; 2] {
2156    [vec![0.0; max_block], vec![0.0; max_block]]
2157}
2158
2159fn push_effects(chain: &mut ProcessorChain, effects: &[AudioEffect]) {
2160    for effect in effects {
2161        match effect {
2162            AudioEffect::Eq { bands } => {
2163                for band in bands {
2164                    push_effect(
2165                        chain,
2166                        Biquad::new(
2167                            match band.kind {
2168                                EqBandKind::Peaking => BiquadKind::Peaking,
2169                                EqBandKind::LowPass => BiquadKind::LowPass,
2170                                EqBandKind::HighPass => BiquadKind::HighPass,
2171                            },
2172                            band.frequency_hz,
2173                            band.q,
2174                            band.gain_db,
2175                        ),
2176                    );
2177                }
2178            }
2179            AudioEffect::Compressor {
2180                threshold_db,
2181                ratio,
2182                attack_ms,
2183                release_ms,
2184                lookahead_ms,
2185            } => push_effect(
2186                chain,
2187                Compressor::new(
2188                    *threshold_db,
2189                    *ratio,
2190                    *attack_ms,
2191                    *release_ms,
2192                    *lookahead_ms,
2193                ),
2194            ),
2195            AudioEffect::Reverb {
2196                room_size,
2197                damping,
2198                mix,
2199            } => push_effect(chain, SchroederReverb::new(*room_size, *damping, *mix)),
2200        }
2201    }
2202}
2203
2204/// Adds one of the model's effects, each of which reads one input per output.
2205fn push_effect(chain: &mut ProcessorChain, effect: impl Processor + 'static) {
2206    chain
2207        .push(effect)
2208        .expect("a model effect reads one input per output");
2209}
2210
2211/// Converts an absolute sample index to exact timeline ticks.
2212fn ticks_at(sample: i64, sample_rate: u32) -> i64 {
2213    checked_ticks_at(sample, sample_rate).unwrap_or(i64::MAX)
2214}
2215
2216/// The tick position of a sample, or `None` when it does not fit in ticks.
2217fn checked_ticks_at(sample: i64, sample_rate: u32) -> Option<i64> {
2218    let rate = i128::from(sample_rate.max(1));
2219    i64::try_from((i128::from(sample) * i128::from(TICKS_PER_SECOND)).div_euclid(rate)).ok()
2220}
2221
2222/// One source block a clip's read takes its samples from, and where in
2223/// `clip_a` they go: see [`AudioRenderer::read_source`].
2224struct SourceRead<'a> {
2225    planes: &'a [Vec<f32>],
2226    downmix: &'a Downmix,
2227    /// The source sample the planes start at.
2228    start: i64,
2229    offset: usize,
2230    count: usize,
2231}
2232
2233/// How one block reads one clip: see [`AudioRenderer::clip_read`].
2234struct ClipRead {
2235    /// The clip's bus.
2236    bus: usize,
2237    /// Where the bus's input starts in the block.
2238    bus_input: i64,
2239    /// The clip's slot, `(bus, position)`, when it has one.
2240    slot: Option<(usize, usize)>,
2241    /// The project sample the clip's first read sample is at.
2242    first: i64,
2243    window: ClipWindow,
2244}
2245
2246/// Where a clip lands in one block of its chain's input.
2247#[derive(Clone, Copy)]
2248struct ClipWindow {
2249    /// First block sample the clip covers.
2250    offset: usize,
2251    /// Samples the chain runs for: the clip, then its flush.
2252    count: usize,
2253    /// Of those, samples read from the source; the rest are silence.
2254    content: usize,
2255}
2256
2257/// The first tick at or after a sample's position, or `None` when it does
2258/// not fit in ticks.
2259fn ticks_at_or_after(sample: i64, sample_rate: u32) -> Option<i64> {
2260    let rate = i128::from(sample_rate.max(1));
2261    let numerator = i128::from(sample) * i128::from(TICKS_PER_SECOND);
2262    i64::try_from(numerator.div_euclid(rate) + i128::from(numerator.rem_euclid(rate) != 0)).ok()
2263}
2264
2265/// Adds `source_range` of `source_id` to `needs`, merged with every need of
2266/// that source it overlaps or touches.
2267fn merge_need(needs: &mut Vec<AudioNeed>, source_id: &SourceId, mut source_range: TimeRange) {
2268    // Only intervals that overlap or touch merge. Two clips cut from different parts of one
2269    // source meet at a join, and a block spanning it must not ask for everything in between. A
2270    // clip can bridge two needs already collected, so it absorbs every one it touches.
2271    while let Some(index) = needs.iter().position(|need| {
2272        need.source_id == *source_id
2273            && need.source_range.start() <= source_range.end()
2274            && source_range.start() <= need.source_range.end()
2275    }) {
2276        let existing = needs.remove(index).source_range;
2277        let merged_start = existing.start().min(source_range.start());
2278        let merged_end = existing.end().max(source_range.end());
2279        if let Ok(merged) = TimeRange::new(merged_start, merged_end) {
2280            source_range = merged;
2281        }
2282    }
2283    needs.push(AudioNeed {
2284        source_id: source_id.clone(),
2285        source_range,
2286    });
2287}
2288
2289/// Calls `request` with the source sample window, `[from, to)`, of each
2290/// read [`AudioRenderer::fill_clip`] makes of `count` project samples of a
2291/// clip from `first_sample`: the same maps, cut at the same loop wraps.
2292fn source_requests(
2293    clip: &ActiveAudioClip,
2294    first_sample: i64,
2295    count: usize,
2296    rate: u32,
2297    mut request: impl FnMut(i64, i64),
2298) {
2299    if count == 0 {
2300        return;
2301    }
2302    match clip.time_map {
2303        TimeMap::Freeze { .. } => {}
2304        TimeMap::Forward | TimeMap::Reverse => {
2305            let reversed = clip.time_map == TimeMap::Reverse;
2306            if let Some((from, to)) = SourceMap::stretched(clip, reversed, rate)
2307                .and_then(|map| map.request(first_sample, count))
2308            {
2309                request(from, to);
2310            }
2311        }
2312        TimeMap::Loop { mode } => {
2313            loop_pieces(clip, mode, first_sample, count, rate, |piece| {
2314                if let Some((from, to)) = piece.map.request(piece.sample, piece.length) {
2315                    request(from, to);
2316                }
2317            });
2318        }
2319    }
2320}
2321
2322/// One pass's share of a looping clip's read.
2323struct LoopPiece {
2324    /// The project sample the piece starts at, and its offset into the read.
2325    sample: i64,
2326    index: usize,
2327    length: usize,
2328    /// The pass, and the timeline ticks it starts and ends at.
2329    pass: i64,
2330    pass_start: i64,
2331    pass_end: i64,
2332    /// The first project sample of the next pass.
2333    piece_end: i64,
2334    map: SourceMap,
2335}
2336
2337/// Cuts a looping clip's read of `count` project samples from
2338/// `first_sample` at its wraps, and calls `piece` with each pass's share:
2339/// within a pass the source position is linear. Returns false where the clip
2340/// cannot be mapped.
2341fn loop_pieces(
2342    clip: &ActiveAudioClip,
2343    mode: LoopMode,
2344    first_sample: i64,
2345    count: usize,
2346    rate: u32,
2347    mut piece: impl FnMut(LoopPiece),
2348) -> bool {
2349    let Some(period) = clip.source_range.duration().map(MediaTime::ticks) else {
2350        return false;
2351    };
2352    if period <= 0 {
2353        return false;
2354    }
2355    let start = clip.timeline_range.start().ticks();
2356    let mut index = 0;
2357    while index < count {
2358        let sample = first_sample + i64::try_from(index).unwrap_or(i64::MAX);
2359        let pass = (ticks_at(sample, rate) - start).div_euclid(period);
2360        let Some(pass_start) = pass
2361            .checked_mul(period)
2362            .and_then(|ticks| ticks.checked_add(start))
2363        else {
2364            return false;
2365        };
2366        let pass_end = pass_start.saturating_add(period);
2367        let piece_end = sample_at_or_after(pass_end, rate);
2368        let length = usize::try_from(piece_end.saturating_sub(sample))
2369            .unwrap_or(usize::MAX)
2370            .clamp(1, count - index);
2371        let reversed = mode == LoopMode::PingPong && pass.rem_euclid(2) == 1;
2372        let Some(map) = SourceMap::pass(clip, pass_start, reversed, rate) else {
2373            return false;
2374        };
2375        piece(LoopPiece {
2376            sample,
2377            index,
2378            length,
2379            pass,
2380            pass_start,
2381            pass_end,
2382            piece_end,
2383            map,
2384        });
2385        index += length;
2386    }
2387    true
2388}
2389
2390/// Returns the first sample index at or after a tick position.
2391fn sample_at_or_after(ticks: i64, sample_rate: u32) -> i64 {
2392    let rate = i128::from(sample_rate.max(1));
2393    let numerator = i128::from(ticks) * rate;
2394    let denominator = i128::from(TICKS_PER_SECOND);
2395    let value =
2396        numerator.div_euclid(denominator) + i128::from(numerator.rem_euclid(denominator) != 0);
2397    i64::try_from(value).unwrap_or(i64::MAX)
2398}
2399
2400/// One project channel's sample at source sample `index`, folded from the
2401/// source's planes by `mix`.
2402///
2403/// The terms are summed in plane order, so the result is the same on every
2404/// platform.
2405fn mixed_sample(mix: &ChannelMix, planes: &[Vec<f32>], index: i64) -> f32 {
2406    if let Some(plane) = mix.unity() {
2407        return copied(&planes[plane], index);
2408    }
2409    let mut sum = 0.0;
2410    for &(plane, weight) in mix.terms() {
2411        sum += weight * copied(&planes[plane], index);
2412    }
2413    sum
2414}
2415
2416/// One project channel's sample filtered from source samples `first ..` by
2417/// `weights` ([`Interpolator::weights`]), folded from the source's planes by
2418/// `mix` as [`mixed_sample`] folds them.
2419fn filtered_sample(mix: &ChannelMix, planes: &[Vec<f32>], first: i64, weights: &[f64]) -> f32 {
2420    if let Some(plane) = mix.unity() {
2421        return filtered(&planes[plane], first, weights);
2422    }
2423    let mut sum = 0.0;
2424    for &(plane, weight) in mix.terms() {
2425        sum += weight * filtered(&planes[plane], first, weights);
2426    }
2427    sum
2428}
2429
2430/// Source sample `index` of `plane`. A sample outside the plane is silence: a
2431/// block is read only where it covers what the clip needs or says the source
2432/// is silent there, so the mix is the same whether a provider gives that
2433/// silence as zeros or says so. A non-finite sample is silence.
2434fn copied(plane: &[f32], index: i64) -> f32 {
2435    let value = usize::try_from(index)
2436        .ok()
2437        .and_then(|index| plane.get(index).copied())
2438        .unwrap_or(0.0);
2439    if value.is_finite() { value } else { 0.0 }
2440}
2441
2442/// The weighted sum of source samples `first .. first + weights.len()` of
2443/// `plane`, in order. As in [`copied`], a sample outside the plane or not
2444/// finite is silence.
2445fn filtered(plane: &[f32], first: i64, weights: &[f64]) -> f32 {
2446    let mut sum = weighed(plane, first, weights, false);
2447    if !sum.is_finite() {
2448        sum = weighed(plane, first, weights, true);
2449    }
2450    #[allow(clippy::cast_possible_truncation)]
2451    let value = sum as f32;
2452    if value.is_finite() { value } else { 0.0 }
2453}
2454
2455/// The sum behind [`filtered`]. Four running sums, one per tap in turn,
2456/// added pairwise at the end: a fixed order, so the result is the same on
2457/// every platform, that does not wait on one long chain of additions.
2458fn weighed(plane: &[f32], first: i64, weights: &[f64], finite_only: bool) -> f64 {
2459    let inside = usize::try_from(first).ok().filter(|&from| {
2460        from.checked_add(weights.len())
2461            .is_some_and(|end| end <= plane.len())
2462    });
2463    let mut sums = [0.0_f64; 4];
2464    match inside {
2465        Some(from) if !finite_only => {
2466            let (weight_quads, weight_rest) = weights.as_chunks::<4>();
2467            let (sample_quads, sample_rest) = plane[from..from + weights.len()].as_chunks::<4>();
2468            for (weight, value) in weight_quads.iter().zip(sample_quads) {
2469                for lane in 0..4 {
2470                    sums[lane] += weight[lane] * f64::from(value[lane]);
2471                }
2472            }
2473            for (lane, (weight, value)) in weight_rest.iter().zip(sample_rest).enumerate() {
2474                sums[lane] += weight * f64::from(*value);
2475            }
2476        }
2477        _ => {
2478            for (index, (at, weight)) in (first..).zip(weights).enumerate() {
2479                let value = usize::try_from(at)
2480                    .ok()
2481                    .and_then(|at| plane.get(at).copied())
2482                    .unwrap_or(0.0);
2483                if value.is_finite() {
2484                    sums[index % 4] += weight * f64::from(value);
2485                }
2486            }
2487        }
2488    }
2489    (sums[0] + sums[1]) + (sums[2] + sums[3])
2490}
2491
2492/// Where each project sample of a clip reads its source, in source samples.
2493///
2494/// Project sample `n` is at `n / rate` seconds; the clip maps its timeline
2495/// range linearly onto its source range; source sample `p` is at `p / rate`
2496/// seconds. Composed, `p(n) = (a + n * b) / d` with every term an integer, so
2497/// the position is exact and the tick grid never enters it.
2498struct SourceMap {
2499    a: i128,
2500    b: i128,
2501    d: i128,
2502}
2503
2504impl SourceMap {
2505    /// A forward or reverse clip: its timeline range stretched over its
2506    /// source range. Reversed, it reads the forward clip's positions in the
2507    /// opposite order, sample for sample.
2508    fn stretched(clip: &ActiveAudioClip, reversed: bool, rate: u32) -> Option<Self> {
2509        let timeline = clip.timeline_range;
2510        let forward = Self::line(
2511            i128::from(timeline.start().ticks()),
2512            i128::from(clip.source_range.start().ticks()),
2513            i128::from(timeline.duration()?.ticks()),
2514            i128::from(clip.source_range.duration()?.ticks()),
2515            rate,
2516        )?;
2517        if !reversed {
2518            return Some(forward);
2519        }
2520        forward.mirrored(
2521            sample_at_or_after(timeline.start().ticks(), rate),
2522            sample_at_or_after(timeline.end().ticks(), rate),
2523        )
2524    }
2525
2526    /// One pass of a loop starting at timeline tick `pass_start`: the source
2527    /// range at its own speed, forward, or reversed as [`Self::stretched`]
2528    /// reverses a clip.
2529    fn pass(clip: &ActiveAudioClip, pass_start: i64, reversed: bool, rate: u32) -> Option<Self> {
2530        let period = clip.source_range.duration()?.ticks();
2531        let forward = Self::line(
2532            i128::from(pass_start),
2533            i128::from(clip.source_range.start().ticks()),
2534            i128::from(period),
2535            i128::from(period),
2536            rate,
2537        )?;
2538        if !reversed {
2539            return Some(forward);
2540        }
2541        forward.mirrored(
2542            sample_at_or_after(pass_start, rate),
2543            sample_at_or_after(pass_start.checked_add(period)?, rate),
2544        )
2545    }
2546
2547    /// This map read backwards over project samples `first..last`: sample
2548    /// `n` reads what sample `first + last - 1 - n` reads here.
2549    fn mirrored(&self, first: i64, last: i64) -> Option<Self> {
2550        let pivot = i128::from(first)
2551            .checked_add(i128::from(last))?
2552            .checked_sub(1)?;
2553        Some(Self {
2554            a: self.a.checked_add(pivot.checked_mul(self.b)?)?,
2555            b: self.b.checked_neg()?,
2556            d: self.d,
2557        })
2558    }
2559
2560    /// The line through source tick `s0` at timeline tick `t0`, advancing
2561    /// `source` ticks per `timeline` ticks.
2562    fn line(t0: i128, s0: i128, timeline: i128, source: i128, rate: u32) -> Option<Self> {
2563        if timeline <= 0 || source <= 0 {
2564            return None;
2565        }
2566        let divisor = gcd(timeline, source);
2567        let (timeline, source) = (timeline / divisor, source / divisor);
2568        let rate = i128::from(rate);
2569        let ticks = i128::from(TICKS_PER_SECOND);
2570        // p(n) = (s0 + (n * TPS / rate - t0) * source / timeline) * rate / TPS
2571        let a = rate.checked_mul(
2572            s0.checked_mul(timeline)?
2573                .checked_sub(t0.checked_mul(source)?)?,
2574        )?;
2575        let b = ticks.checked_mul(source)?;
2576        let d = ticks.checked_mul(timeline)?;
2577        Some(Self { a, b, d })
2578    }
2579
2580    /// Source sample index at or before project sample `sample`, and how far
2581    /// toward the next one it is.
2582    fn position(&self, sample: i64) -> Option<(i64, f64)> {
2583        let numerator = self
2584            .a
2585            .checked_add(i128::from(sample).checked_mul(self.b)?)?;
2586        let whole = i64::try_from(numerator.div_euclid(self.d)).ok()?;
2587        let remainder = numerator.rem_euclid(self.d);
2588        #[allow(clippy::cast_precision_loss)]
2589        let fraction = remainder as f64 / self.d as f64;
2590        Some((whole, fraction))
2591    }
2592
2593    /// The filter a read through this map interpolates with, or `None` where
2594    /// it copies: at unity speed with every position on a source sample.
2595    fn interpolator(&self) -> Option<Interpolator> {
2596        if self.b.unsigned_abs() == self.d.unsigned_abs() && self.a.rem_euclid(self.d) == 0 {
2597            return None;
2598        }
2599        #[allow(clippy::cast_precision_loss)]
2600        let speed = self.b.unsigned_abs() as f64 / self.d.unsigned_abs() as f64;
2601        Some(Interpolator::new(speed))
2602    }
2603
2604    /// The source sample window reading `count` project samples from
2605    /// `first_sample` takes, `[from, to)`: every source sample the reads
2606    /// weigh, the sample itself where a read copies and the taps of its
2607    /// [`Interpolator`] either side where it filters. The first and last
2608    /// samples read bound the positions either way round, as the map is
2609    /// linear. Sources begin at sample zero.
2610    fn request(&self, first_sample: i64, count: usize) -> Option<(i64, i64)> {
2611        let last_sample = first_sample.checked_add(i64::try_from(count).ok()?.checked_sub(1)?)?;
2612        let (first, _) = self.position(first_sample)?;
2613        let (last, _) = self.position(last_sample)?;
2614        let (low, high) = (first.min(last), first.max(last));
2615        let Some(interpolator) = self.interpolator() else {
2616            return Some((low.max(0), high.saturating_add(1)));
2617        };
2618        let half = interpolator.half_taps();
2619        Some((
2620            low.saturating_sub(half - 1).max(0),
2621            high.saturating_add(half + 1),
2622        ))
2623    }
2624
2625    /// A cursor at project sample `sample` that steps one project sample at a
2626    /// time, or `None` when a position or the divisor does not fit it.
2627    fn cursor(&self, sample: i64) -> Option<RationalCursor> {
2628        let numerator = self
2629            .a
2630            .checked_add(i128::from(sample).checked_mul(self.b)?)?;
2631        RationalCursor::new(numerator, self.b, self.d)
2632    }
2633
2634    /// What [`Self::position`] returns at the cursor's sample, bit for bit.
2635    fn fraction(&self, cursor: &RationalCursor) -> (i64, f64) {
2636        // Integer-to-float conversion rounds the value, not its type, so the narrower remainder
2637        // converts exactly as the 128-bit one does.
2638        #[allow(clippy::cast_precision_loss)]
2639        let fraction = cursor.remainder as f64 / self.d as f64;
2640        (cursor.whole, fraction)
2641    }
2642}
2643
2644/// Walks `(numerator + n * step) / divisor` over consecutive `n`, keeping the
2645/// floor and the remainder, with an addition per step instead of a 128-bit
2646/// division.
2647///
2648/// The caller bounds the walk so every floor it reaches fits in `i64`.
2649#[derive(Clone, Copy, Debug)]
2650struct RationalCursor {
2651    whole: i64,
2652    remainder: u64,
2653    step_whole: i64,
2654    step_remainder: u64,
2655    divisor: u64,
2656}
2657
2658impl RationalCursor {
2659    fn new(numerator: i128, step: i128, divisor: i128) -> Option<Self> {
2660        // A divisor below 2^63 keeps `remainder + step_remainder` in a u64.
2661        if divisor <= 0 || divisor > i128::from(i64::MAX) {
2662            return None;
2663        }
2664        Some(Self {
2665            whole: i64::try_from(numerator.div_euclid(divisor)).ok()?,
2666            remainder: u64::try_from(numerator.rem_euclid(divisor)).ok()?,
2667            step_whole: i64::try_from(step.div_euclid(divisor)).ok()?,
2668            step_remainder: u64::try_from(step.rem_euclid(divisor)).ok()?,
2669            divisor: u64::try_from(divisor).ok()?,
2670        })
2671    }
2672
2673    /// After how many steps the remainder comes back to where it is.
2674    const fn period(&self) -> u64 {
2675        let (mut a, mut b) = (self.step_remainder, self.divisor);
2676        while b != 0 {
2677            (a, b) = (b, a % b);
2678        }
2679        self.divisor / a
2680    }
2681
2682    fn advance(&mut self) {
2683        self.whole = self.whole.wrapping_add(self.step_whole);
2684        self.remainder += self.step_remainder;
2685        if self.remainder >= self.divisor {
2686            self.remainder -= self.divisor;
2687            self.whole = self.whole.wrapping_add(1);
2688        }
2689    }
2690}
2691
2692/// Timeline ticks of consecutive samples from `sample`: [`ticks_at`] without
2693/// a division per sample.
2694fn tick_cursor(sample: i64, sample_rate: u32) -> RationalCursor {
2695    let ticks = i128::from(TICKS_PER_SECOND);
2696    RationalCursor::new(
2697        i128::from(sample) * ticks,
2698        ticks,
2699        i128::from(sample_rate.max(1)),
2700    )
2701    // Only a sample past the tick range, which `render` rejects, lands here; it saturates as
2702    // `ticks_at` does.
2703    .unwrap_or(RationalCursor {
2704        whole: i64::MAX,
2705        remainder: 0,
2706        step_whole: 0,
2707        step_remainder: 0,
2708        divisor: 1,
2709    })
2710}
2711
2712fn gcd(mut a: i128, mut b: i128) -> i128 {
2713    while b != 0 {
2714        (a, b) = (b, a % b);
2715    }
2716    a.abs().max(1)
2717}
2718
2719/// Project samples between the points where automation is evaluated.
2720///
2721/// About 0.7 ms at 48 kHz: shorter than any gain or pan move an editor
2722/// authors, and long enough that evaluating keyframes (a cubic Bezier solve
2723/// each) costs a small part of mixing the samples.
2724const ENVELOPE_INTERVAL: i64 = 32;
2725
2726/// Writes the `N` multipliers `at` gives each of the `count` samples from
2727/// project sample `first` into the first `N` planes of `envelope`, evaluating
2728/// `at` at control points rather than at every sample.
2729///
2730/// The control points are the multiples of [`ENVELOPE_INTERVAL`]. Between
2731/// two of them the multiplier moves linearly from one point's value to the
2732/// next's, unless the envelope has a corner or a step there: `next_knot`
2733/// gives the first tick after a tick where it may (a keyframe, a fade's or the
2734/// clip's edge), and an interval that holds one is evaluated at every sample,
2735/// so a held keyframe changes the value on its own sample. Each sample's value
2736/// depends only on its project sample, never on where a block starts, so
2737/// blocks of any size, seeks, and every platform mix the same bits.
2738fn fill_envelope<const N: usize>(
2739    envelope: &mut [Vec<f32>],
2740    first: i64,
2741    count: usize,
2742    sample_rate: u32,
2743    next_knot: impl Fn(i64) -> Option<i64>,
2744    at: impl Fn(MediaTime) -> [f32; N],
2745) {
2746    #[allow(clippy::cast_precision_loss)]
2747    let fraction = 1.0 / ENVELOPE_INTERVAL as f32;
2748    // The first knot after the last interval's start; `None` when there is none, and unknown
2749    // until the first interval.
2750    let mut knot: Option<Option<i64>> = None;
2751    // The last control point evaluated, which the next interval starts from.
2752    let mut point: Option<(i64, [f32; N])> = None;
2753    let mut written = 0;
2754    while written < count {
2755        let sample = first.saturating_add(i64::try_from(written).unwrap_or(i64::MAX));
2756        let start = sample.div_euclid(ENVELOPE_INTERVAL) * ENVELOPE_INTERVAL;
2757        let stop = start.saturating_add(ENVELOPE_INTERVAL);
2758        let run = usize::try_from(stop - sample)
2759            .unwrap_or(count)
2760            .clamp(1, count - written);
2761        let (from, to) = (ticks_at(start, sample_rate), ticks_at(stop, sample_rate));
2762        let next = match knot {
2763            Some(next) if next.is_none_or(|next| next > from) => next,
2764            _ => next_knot(from),
2765        };
2766        knot = Some(next);
2767        let outputs = written..written + run;
2768        if next.is_some_and(|next| next <= to) {
2769            let mut ticks = tick_cursor(sample, sample_rate);
2770            for index in outputs {
2771                let value = at(MediaTime::from_ticks(ticks.whole));
2772                ticks.advance();
2773                for (plane, value) in envelope.iter_mut().zip(value) {
2774                    plane[index] = value;
2775                }
2776            }
2777            #[cfg(test)]
2778            ENVELOPE_POINTS.set(ENVELOPE_POINTS.get() + run);
2779        } else {
2780            let left = match point {
2781                Some((at_sample, value)) if at_sample == start => value,
2782                _ => {
2783                    #[cfg(test)]
2784                    ENVELOPE_POINTS.set(ENVELOPE_POINTS.get() + 1);
2785                    at(MediaTime::from_ticks(from))
2786                }
2787            };
2788            let right = at(MediaTime::from_ticks(to));
2789            #[cfg(test)]
2790            ENVELOPE_POINTS.set(ENVELOPE_POINTS.get() + 1);
2791            point = Some((stop, right));
2792            #[allow(clippy::cast_precision_loss)]
2793            let along = (sample - start) as f32;
2794            for (plane, (left, right)) in envelope.iter_mut().zip(left.into_iter().zip(right)) {
2795                let slope = right - left;
2796                for (position, output) in plane[outputs.clone()].iter_mut().enumerate() {
2797                    #[allow(clippy::cast_precision_loss)]
2798                    let position = along + position as f32;
2799                    *output = left + slope * (position * fraction);
2800                }
2801            }
2802        }
2803        written += run;
2804    }
2805}
2806
2807#[cfg(test)]
2808thread_local! {
2809    /// How many times [`fill_envelope`] evaluated an envelope on this thread.
2810    static ENVELOPE_POINTS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
2811    /// How many bus channels this thread cleared for a block, and how many it
2812    /// summed into the master.
2813    static BUS_PLANES_CLEARED: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
2814    static BUS_PLANES_SUMMED: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
2815}
2816
2817/// The earlier of two optional ticks, where `None` is never.
2818fn earliest(left: Option<i64>, right: Option<i64>) -> Option<i64> {
2819    match (left, right) {
2820        (Some(left), Some(right)) => Some(left.min(right)),
2821        (left, right) => left.or(right),
2822    }
2823}
2824
2825/// The first keyframe of `animation` after `ticks`, where its value may turn
2826/// or step. A constant, or a single keyframe, has none.
2827fn next_keyframe(animation: &Animated<f32>, ticks: i64) -> Option<i64> {
2828    match animation {
2829        Animated::Keyframes { keyframes } if keyframes.len() > 1 => {
2830            let next = keyframes.partition_point(|keyframe| keyframe.time.ticks() <= ticks);
2831            keyframes.get(next).map(|keyframe| keyframe.time.ticks())
2832        }
2833        _ => None,
2834    }
2835}
2836
2837/// The first edge of a clip's fades after `ticks`: where a fade starts or
2838/// ends, which [`fade_gain`] turns at.
2839fn next_fade_edge(clip: &ActiveAudioClip, ticks: i64) -> Option<i64> {
2840    let start = clip.timeline_range.start().ticks();
2841    let end = clip.timeline_range.end().ticks();
2842    let fade_in = clip.audio.fade_in.ticks();
2843    let fade_out = clip.audio.fade_out.ticks();
2844    let mut next = None;
2845    if fade_in > 0 {
2846        for edge in [start, start.saturating_add(fade_in)] {
2847            if edge > ticks {
2848                next = earliest(next, Some(edge));
2849            }
2850        }
2851    }
2852    if fade_out > 0 {
2853        for edge in [end.saturating_sub(fade_out), end] {
2854            if edge > ticks {
2855                next = earliest(next, Some(edge));
2856            }
2857        }
2858    }
2859    next
2860}
2861
2862/// The value of an authored scalar that does not change over time.
2863///
2864/// One keyframe holds its value everywhere, as [`sample_scalar`] samples it.
2865fn constant_value(animation: &Animated<f32>) -> Option<f32> {
2866    match animation {
2867        Animated::Constant { value } => Some(*value),
2868        Animated::Keyframes { keyframes } => match keyframes.as_slice() {
2869            [only] => Some(only.value),
2870            _ => None,
2871        },
2872    }
2873}
2874
2875/// Whether either fade of a clip changes its gain anywhere in the `count`
2876/// samples from `first`. Where it does not, [`fade_gain`] is exactly one.
2877fn fades_reach(clip: &ActiveAudioClip, first: i64, count: usize, sample_rate: u32) -> bool {
2878    let fade_in = clip.audio.fade_in.ticks();
2879    let fade_out = clip.audio.fade_out.ticks();
2880    if fade_in <= 0 && fade_out <= 0 {
2881        return false;
2882    }
2883    let last = first.saturating_add(i64::try_from(count).unwrap_or(i64::MAX).saturating_sub(1));
2884    // A pre-roll can read before the clip starts, and a clip may end near the largest tick: a
2885    // difference past the tick range is past every fade.
2886    let elapsed = ticks_at(first, sample_rate).saturating_sub(clip.timeline_range.start().ticks());
2887    let remaining = clip
2888        .timeline_range
2889        .end()
2890        .ticks()
2891        .saturating_sub(ticks_at(last, sample_rate));
2892    (fade_in > 0 && elapsed < fade_in) || (fade_out > 0 && remaining < fade_out)
2893}
2894
2895/// Returns the fade multiplier authored on a clip at one timestamp.
2896///
2897/// Both fades use the clip's [`alpha2_model::FadeShape`]. Two overlapping clips
2898/// with equal-power fades crossfade at constant loudness; two linear fades dip
2899/// by 6 dB in the middle, which is the right answer only for correlated audio.
2900fn fade_gain(clip: &ActiveAudioClip, time: MediaTime) -> f32 {
2901    let start = clip.timeline_range.start().ticks();
2902    let end = clip.timeline_range.end().ticks();
2903    let shape = clip.audio.fade_shape;
2904    let mut gain = 1.0_f32;
2905    let fade_in = clip.audio.fade_in.ticks();
2906    if fade_in > 0 {
2907        let elapsed = time.ticks().saturating_sub(start);
2908        if elapsed < fade_in {
2909            gain *= shape.amplitude(ratio(elapsed, fade_in));
2910        }
2911    }
2912    let fade_out = clip.audio.fade_out.ticks();
2913    if fade_out > 0 {
2914        let remaining = end.saturating_sub(time.ticks());
2915        if remaining < fade_out {
2916            gain *= shape.amplitude(ratio(remaining, fade_out));
2917        }
2918    }
2919    gain.clamp(0.0, 1.0)
2920}
2921
2922#[allow(clippy::cast_precision_loss)]
2923fn ratio(value: i64, span: i64) -> f32 {
2924    if span <= 0 {
2925        return 1.0;
2926    }
2927    (value.max(0) as f32 / span as f32).clamp(0.0, 1.0)
2928}
2929
2930/// Returns per-channel constant-power weights normalized to unity at the centre.
2931///
2932/// A centred clip is unmodified, so the clip and track pan stages compose
2933/// without attenuating a default project. A hard pan raises the remaining
2934/// channel by 3 dB, which is the conventional video-editor pan law; the master
2935/// limiter is what bounds the resulting peak.
2936///
2937/// Mono output has nothing to position, so panning is ignored.
2938fn pan_gains(pan: f32, channels: usize) -> [f32; 2] {
2939    if channels < 2 {
2940        return [1.0, 1.0];
2941    }
2942    // In f64, the centre's cos(pi/4) * sqrt(2) rounds to exactly one in f32; in f32 it is
2943    // 0.99999994.
2944    let angle = (f64::from(pan.clamp(-1.0, 1.0)) + 1.0) * core::f64::consts::FRAC_PI_4;
2945    let normalize = core::f64::consts::SQRT_2;
2946    #[allow(clippy::cast_possible_truncation)]
2947    let weights = [
2948        (libm::cos(angle) * normalize) as f32,
2949        (libm::sin(angle) * normalize) as f32,
2950    ];
2951    weights
2952}
2953
2954#[cfg(test)]
2955mod tests;