1use 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
38const MAX_CACHED_WEIGHTS: usize = 1 << 15;
41
42pub const MAX_AUDIO_BLOCK: usize = 8_192;
44
45const _: () = {
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
58pub const MAX_SETTLING_SECONDS: u32 = 30;
68
69#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
72pub enum AudioContinuity {
73 #[default]
79 Exact,
80 ContinueSmallGaps,
94}
95
96#[derive(Clone, Debug, PartialEq, Eq)]
98pub struct AudioRenderStatus {
99 pub start_sample: i64,
101 pub frames: usize,
103 pub missing_sources: Vec<SourceId>,
112}
113
114impl AudioRenderStatus {
115 #[must_use]
117 pub fn is_complete(&self) -> bool {
118 self.missing_sources.is_empty()
119 }
120}
121
122#[derive(Clone, Debug, PartialEq, Eq)]
124pub struct AudioNeed {
125 pub source_id: SourceId,
127 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 output: [Vec<f32>; 2],
140 live: bool,
146 latency: i64,
148 clips: Vec<ClipSlot>,
150 chain_lead: i64,
153 ducker_lead: i64,
155 read: bool,
158}
159
160impl TrackBus {
161 fn active_sidechain(&self, index: usize) -> Option<usize> {
164 self.ducker.as_ref()?;
165 self.sidechain.filter(|&sidechain| sidechain != index)
166 }
167
168 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
183struct BusDucker {
186 ducker: Ducker,
187 output: [Vec<f32>; 2],
188}
189
190struct ClipSlot {
192 id: ClipId,
193 chain: Option<ClipChain>,
194}
195
196struct ClipChain {
206 effects: Vec<AudioEffect>,
207 chain: Option<ProcessorChain>,
208 ran_in: u64,
210 latency: i64,
212 history: u32,
214 next_input: Option<i64>,
216 lead: i64,
218}
219
220pub struct AudioRenderer {
222 layout: ChannelLayout,
223 sample_rate: u32,
224 max_block: usize,
225 buses: Vec<TrackBus>,
228 bus_of_track: HashMap<TrackId, usize>,
232 slot_of_clip: HashMap<ClipId, (usize, usize)>,
234 span_scratch: AudioSpanScratch,
236 master_chain: ProcessorChain,
237 master_latency: i64,
239 lookahead: i64,
241 history: i64,
244 master_lead: i64,
246 limiter_lead: i64,
248 stage_leads: Vec<i64>,
252 master: MasterAudio,
253 limiter: Option<PeakLimiter>,
254 normalization_gain: f32,
257 hold_incomplete: bool,
258 continuity: AudioContinuity,
259 meter: Option<LoudnessMeter>,
261 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 envelope: [Vec<f32>; 3],
272 next_sample: Option<i64>,
274 live_chains: Vec<(usize, usize)>,
276 blocks_mixed: u64,
278 weights: Vec<f64>,
281 phase_weights: Vec<f64>,
283 #[cfg(test)]
285 most_held: (usize, u64),
286 #[cfg(test)]
288 samples_read: usize,
289}
290
291#[derive(Clone, Copy, Debug, PartialEq, Eq)]
294enum Fill {
295 Samples,
297 Presence,
300}
301
302impl AudioRenderer {
303 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 #[must_use]
401 pub const fn sample_rate(&self) -> u32 {
402 self.sample_rate
403 }
404
405 #[must_use]
407 pub const fn channels(&self) -> usize {
408 self.layout.channels()
409 }
410
411 #[must_use]
413 pub fn track_ids(&self) -> Vec<&TrackId> {
414 self.buses.iter().map(|bus| &bus.id).collect()
415 }
416
417 #[must_use]
419 pub const fn max_block(&self) -> usize {
420 self.max_block
421 }
422
423 #[must_use]
425 pub fn channel(&self, index: usize) -> &[f32] {
426 self.output.get(index).map_or(&[], Vec::as_slice)
427 }
428
429 #[must_use]
437 pub fn required_lookahead_samples(&self) -> u32 {
438 u32::try_from(self.lookahead).unwrap_or(u32::MAX)
439 }
440
441 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 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 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 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 #[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 Ok(start
576 .saturating_sub(history)
577 .max(0)
578 .saturating_sub(self.lookahead))
579 }
580
581 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 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 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 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 pub fn set_continuity(&mut self, continuity: AudioContinuity) {
634 if self.continuity != continuity {
635 self.reset();
636 self.continuity = continuity;
637 }
638 }
639
640 #[must_use]
642 pub const fn continuity(&self) -> AudioContinuity {
643 self.continuity
644 }
645
646 pub fn reset(&mut self) {
653 self.next_sample = None;
654 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 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 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 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 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 #[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 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 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 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 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 pub const fn set_hold_incomplete_blocks(&mut self, hold: bool) {
1024 self.hold_incomplete = hold;
1025 }
1026
1027 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 #[must_use]
1058 pub fn loudness_report(&self) -> Option<LoudnessReport> {
1059 self.meter.as_ref().map(LoudnessMeter::report)
1060 }
1061
1062 #[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 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 #[must_use]
1096 pub fn output_loudness_report(&self) -> Option<LoudnessReport> {
1097 self.output_meter.as_ref().map(LoudnessMeter::report)
1098 }
1099
1100 #[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 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 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 fn is_silent_bus(&self, index: usize) -> bool {
1148 self.buses[index].audio.is_silenced(self.any_solo)
1149 }
1150
1151 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 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 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 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 let count = content;
1251 if count == 0 {
1252 return true;
1253 }
1254 let rate = self.sample_rate;
1255 match clip.time_map {
1256 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 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 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 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 #[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 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 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 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 (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 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 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 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 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 #[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 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 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 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 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 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 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
1913fn 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 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
1961fn 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 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
1990fn 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
2003fn 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
2022fn 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#[derive(Clone, Copy)]
2064struct Settling(i64);
2065
2066impl Settling {
2067 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#[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 fn start(target: i64, lead: i64, lookahead: i64) -> i64 {
2096 (target - lead).max(0).saturating_sub(lookahead)
2097 }
2098}
2099
2100fn 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
2122fn 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
2143fn 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
2204fn 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
2211fn ticks_at(sample: i64, sample_rate: u32) -> i64 {
2213 checked_ticks_at(sample, sample_rate).unwrap_or(i64::MAX)
2214}
2215
2216fn 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
2222struct SourceRead<'a> {
2225 planes: &'a [Vec<f32>],
2226 downmix: &'a Downmix,
2227 start: i64,
2229 offset: usize,
2230 count: usize,
2231}
2232
2233struct ClipRead {
2235 bus: usize,
2237 bus_input: i64,
2239 slot: Option<(usize, usize)>,
2241 first: i64,
2243 window: ClipWindow,
2244}
2245
2246#[derive(Clone, Copy)]
2248struct ClipWindow {
2249 offset: usize,
2251 count: usize,
2253 content: usize,
2255}
2256
2257fn 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
2265fn merge_need(needs: &mut Vec<AudioNeed>, source_id: &SourceId, mut source_range: TimeRange) {
2268 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
2289fn 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
2322struct LoopPiece {
2324 sample: i64,
2326 index: usize,
2327 length: usize,
2328 pass: i64,
2330 pass_start: i64,
2331 pass_end: i64,
2332 piece_end: i64,
2334 map: SourceMap,
2335}
2336
2337fn 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
2390fn 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
2400fn 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
2416fn 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
2430fn 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
2442fn 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
2455fn 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
2492struct SourceMap {
2499 a: i128,
2500 b: i128,
2501 d: i128,
2502}
2503
2504impl SourceMap {
2505 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 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 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 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 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 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 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 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 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 fn fraction(&self, cursor: &RationalCursor) -> (i64, f64) {
2636 #[allow(clippy::cast_precision_loss)]
2639 let fraction = cursor.remainder as f64 / self.d as f64;
2640 (cursor.whole, fraction)
2641 }
2642}
2643
2644#[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 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 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
2692fn 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 .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
2719const ENVELOPE_INTERVAL: i64 = 32;
2725
2726fn 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 let mut knot: Option<Option<i64>> = None;
2751 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 static ENVELOPE_POINTS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
2811 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
2817fn 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
2825fn 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
2837fn 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
2862fn 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
2875fn 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 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
2895fn 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
2930fn pan_gains(pan: f32, channels: usize) -> [f32; 2] {
2939 if channels < 2 {
2940 return [1.0, 1.0];
2941 }
2942 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;