mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-10 20:41:25 +00:00
Make the audio preview follow edits that change the sound only, and start it when a silent preview gains sound (#2104)
This commit is contained in:
1 parent
7b5786fdb6
commit
4966534c82
4 files changed
+57
-2
No files matched your search
@@ -40,6 +40,7 @@ impl EffectcraftApp {
|
||||
restore_max,
|
||||
audio_frame: (!plan.video).then_some(plan.first),
|
||||
audio_held: false,
|
||||
silent_project: None,
|
||||
starting: false,
|
||||
starting_since: now,
|
||||
first_cached: None,
|
||||
|
||||
@@ -70,6 +70,7 @@ impl EffectcraftApp {
|
||||
let step = plan.map_or(1, |p| p.step);
|
||||
let fit_ahead = self.frames_that_fit(&c, scale) - 1;
|
||||
self.prefetch_playback(&pass, fit_ahead);
|
||||
self.wake_silent_preview();
|
||||
self.pump_meters(ctx, now);
|
||||
if !self.playback.playing {
|
||||
self.cache_when_idle(ctx, &c, &series, now, input_active, (wa, wb));
|
||||
@@ -254,6 +255,21 @@ impl EffectcraftApp {
|
||||
false
|
||||
}
|
||||
|
||||
/// A silent preview whose project was edited so that it has sound now (a layer unmuted while
|
||||
/// playing, #2104): hold the sound for [`Self::start_held_audio`], which starts it from the
|
||||
/// current frame once the frames ahead are cached. Checked once per project edit.
|
||||
fn wake_silent_preview(&mut self) {
|
||||
let pb = &self.playback;
|
||||
if self.audio.is_some() || !pb.playing || pb.caching || pb.starting || pb.audio_held || !pb.plan.is_some_and(|p| p.video) {
|
||||
return;
|
||||
}
|
||||
if pb.silent_project.as_ref().is_some_and(|p| std::sync::Arc::ptr_eq(p, &self.session.project)) {
|
||||
return;
|
||||
}
|
||||
self.playback.silent_project = Some(self.session.project.clone());
|
||||
self.playback.audio_held = self.audio_plays();
|
||||
}
|
||||
|
||||
/// Held sound starts once the frames ahead are cached again after a stall (see
|
||||
/// `Playback::audio_held`): the prefetch window when the silent preview keeps real time, else
|
||||
/// the rest of the preview.
|
||||
|
||||
@@ -23,7 +23,7 @@ use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread::JoinHandle;
|
||||
|
||||
use effectcraft_engine::project::{ItemId, ItemKind, LayerSource, Project};
|
||||
use effectcraft_engine::project::{GroupKind, ItemId, ItemKind, Layer, LayerSource, Project, PropGroup};
|
||||
use effectcraft_engine::time::{FrameRate, Tick};
|
||||
|
||||
use crate::frames::RenderSource;
|
||||
@@ -342,7 +342,7 @@ fn audio_changed(old: &Project, new: &Project, comp: ItemId) -> bool {
|
||||
|| x.switches.solo != y.switches.solo
|
||||
|| x.switches.effects != y.switches.effects
|
||||
|| x.props.sub("audio") != y.props.sub("audio")
|
||||
|| x.effects() != y.effects()
|
||||
|| audio_effects(x) != audio_effects(y)
|
||||
|| match &x.source {
|
||||
LayerSource::Footage { item } => footage_differs(old, new, *item),
|
||||
LayerSource::Comp { item } => comps_differ(old, new, *item, depth + 1),
|
||||
@@ -350,6 +350,12 @@ fn audio_changed(old: &Project, new: &Project, comp: ItemId) -> bool {
|
||||
}
|
||||
})
|
||||
}
|
||||
/// The layer's audio effects (Effect > Audio), the only effects the mixer applies; a video effect
|
||||
/// edit leaves them as they were.
|
||||
fn audio_effects(l: &Layer) -> Vec<&PropGroup> {
|
||||
let is_audio = |g: &&PropGroup| matches!(&g.kind, GroupKind::Effect { effect } if effectcraft_engine::effects::audio_fx::is_audio_effect(effect));
|
||||
l.effects().into_iter().flat_map(|fx| fx.groups()).filter(is_audio).collect()
|
||||
}
|
||||
/// The footage item's audio availability changed (relinked, missing, no audio).
|
||||
fn footage_differs(old: &Project, new: &Project, item: ItemId) -> bool {
|
||||
let f = |p: &Project| match p.item(item).map(|i| &i.kind) {
|
||||
@@ -763,6 +769,36 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// #2104: a video effect edit leaves the mix alone, an audio effect changes it.
|
||||
#[test]
|
||||
fn only_audio_effects_change_the_audio() {
|
||||
use effectcraft_engine::color::Label;
|
||||
use effectcraft_engine::project::build::{self, Ids};
|
||||
use effectcraft_engine::project::{Comp, Footage, FootageKind};
|
||||
|
||||
let mut p = Project::default();
|
||||
let footage =
|
||||
Footage { path: "tone.flac".into(), kind: FootageKind::Audio, duration: Tick::from_seconds_f64(60.0), has_audio: true, ..Default::default() };
|
||||
let item = p.add_item("tone.flac", Label::SeaFoam, None, ItemKind::Footage(footage));
|
||||
let mut comp = Comp::new(64, 36, FrameRate::new(30, 1), Tick::from_seconds_f64(10.0));
|
||||
let layer = build::layer(&mut p, &comp, "tone.flac", LayerSource::Footage { item }, (0, 0), None);
|
||||
comp.layers = vec![layer].into();
|
||||
let cid = p.add_item("Comp", Label::Sandstone, None, ItemKind::Comp(comp.into()));
|
||||
let with_effect = |id: &str| {
|
||||
let mut edited = p.clone();
|
||||
let spec = effectcraft_engine::effects::find(id).expect("the effect exists");
|
||||
let mut next = edited.next_id;
|
||||
let g = effectcraft_engine::effects::instantiate(spec, &mut Ids(&mut next), spec.name, [64.0, 36.0]);
|
||||
edited.next_id = next;
|
||||
if let Some(fx) = edited.comp_mut(cid).and_then(|c| c.layers.first_mut()).and_then(|l| l.props.sub_mut("effects")) {
|
||||
fx.children.push(g.into());
|
||||
}
|
||||
edited
|
||||
};
|
||||
assert!(!audio_changed(&p, &with_effect("ec.blur.gaussian"), cid), "a video effect must not drop the sound");
|
||||
assert!(audio_changed(&p, &with_effect("ec.audio.backwards"), cid), "an audio effect must be followed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn meter_decays_and_holds_peaks() {
|
||||
let mut m = Meter::default();
|
||||
|
||||
@@ -196,6 +196,8 @@ pub struct Playback {
|
||||
/// fits in the RAM preview) is cached, so it never plays over skipped frames (#103), or
|
||||
/// sooner once the silent preview keeps real time with the frames ahead cached (#208).
|
||||
pub audio_held: bool,
|
||||
/// The project last checked for a silent preview that became audible (#2104).
|
||||
pub silent_project: Option<Arc<effectcraft_engine::project::Project>>,
|
||||
/// Starting a preview with sound (#947): the playhead stays on the first frame while the
|
||||
/// frames ahead render (the prefetch window, at most what fits in the RAM preview), then
|
||||
/// sound and picture start together from it. Without this the picture played on silently
|
||||
|
||||
Reference in new issue
Block a user