mirror of
https://github.com/storytold/soundcraft.git
synced 2026-10-27 03:56:45 -06:00
Keep armed tracks' meters live while the transport is stopped
Record-enabled and input-monitored tracks were heard while stopped, but their meters froze (#42). The audio thread returned before copying the meters whenever it wasn't playing. The UI faded every meter to zero while stopped and stopped redrawing once they reached zero. After a selection had played to its end, the stale end point also silenced input monitoring until the next Play. The audio thread now copies the meters for every block it renders and forgets the end point and loop when playback stops. While a track is armed or monitoring input, the UI keeps feeding and redrawing its meters, and the Mix window's per-channel meters include armed tracks.
This commit is contained in:
@@ -171,6 +171,8 @@ impl AudioState {
|
||||
}
|
||||
Cmd::Stop => {
|
||||
self.playing = false;
|
||||
self.end = None;
|
||||
self.looped = None;
|
||||
self.mix.reset();
|
||||
}
|
||||
Cmd::Locate(at) => {
|
||||
@@ -209,6 +211,7 @@ impl AudioState {
|
||||
} else if let Some(e) = self.end {
|
||||
if self.pos >= e {
|
||||
self.playing = false;
|
||||
self.end = None;
|
||||
self.shared.playing.store(false, Ordering::Relaxed);
|
||||
n = 0;
|
||||
} else {
|
||||
@@ -239,11 +242,6 @@ impl AudioState {
|
||||
}
|
||||
}
|
||||
self.pending_len = n;
|
||||
if !self.playing {
|
||||
return;
|
||||
}
|
||||
self.pos += n as i64;
|
||||
self.shared.position.store(self.pos, Ordering::Relaxed);
|
||||
if let Ok(mut m) = self.shared.meters.try_lock() {
|
||||
m.main.copy_from(&self.mix.main_meter);
|
||||
for (k, v) in &self.mix.meters {
|
||||
@@ -255,6 +253,10 @@ impl AudioState {
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.playing {
|
||||
self.pos += n as i64;
|
||||
self.shared.position.store(self.pos, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
/// Fill an interleaved device buffer.
|
||||
@@ -896,4 +898,30 @@ mod tests {
|
||||
assert!(p <= 1000, "position {p} should stay inside the loop");
|
||||
assert!(shared.playing.load(Ordering::Relaxed));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn armed_tracks_are_heard_and_metered_after_playback_stops() {
|
||||
use soundcraft_model::{ChannelFormat, TrackKind};
|
||||
let mut s = soundcraft_model::Session::default();
|
||||
let t = s.add_track(TrackKind::Audio, ChannelFormat::Mono, None);
|
||||
s.track_mut(t).unwrap().mixer.record_arm = true;
|
||||
let shared = Arc::new(Shared {
|
||||
position: AtomicI64::new(0),
|
||||
playing: AtomicBool::new(false),
|
||||
speed: AtomicU32::new(1000),
|
||||
meters: Mutex::new(MeterSnapshot::default()),
|
||||
});
|
||||
let (tx, rx) = channel();
|
||||
let mut st = AudioState::new(rx, Arc::clone(&shared), Arc::new(s), 48_000.0, channel().0);
|
||||
let ring = record::InputRing::new(1, 48_000);
|
||||
tx.send(Cmd::Input(Arc::clone(&ring))).unwrap();
|
||||
tx.send(Cmd::Play { from: 0, end: Some(1000), looped: None }).unwrap();
|
||||
let mut out = vec![0.0f32; 4096];
|
||||
st.fill(&mut out, 2);
|
||||
assert!(!shared.playing.load(Ordering::Relaxed));
|
||||
ring.push(&[0.5; 4096]);
|
||||
st.fill(&mut out, 2);
|
||||
assert!(out.iter().any(|x| x.abs() > 0.1), "the input is heard");
|
||||
assert!(shared.meters.lock().unwrap().tracks.get(&t).is_some_and(|m| m.peak[0] > 0.1), "and metered");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -512,6 +512,12 @@ impl SoundApp {
|
||||
best.map(|(_, p)| p)
|
||||
}
|
||||
|
||||
/// Whether the meters are live: while playing, and while armed or input-monitored tracks are heard.
|
||||
fn meters_live(&self) -> bool {
|
||||
self.engine.transport.playing
|
||||
|| (self.recorder.is_some() && self.engine.session().tracks.iter().any(|t| t.mixer.input_monitor || t.mixer.record_arm))
|
||||
}
|
||||
|
||||
/// Open the input device once (for recording and input monitoring) and feed the player.
|
||||
fn ensure_input(&mut self) {
|
||||
if self.recorder.is_some() || self.recorder_failed || self.player.is_none() {
|
||||
@@ -749,7 +755,7 @@ impl SoundApp {
|
||||
self.stop_play();
|
||||
}
|
||||
}
|
||||
if !self.engine.transport.playing {
|
||||
if !self.meters_live() {
|
||||
for d in self.meters.values_mut() {
|
||||
feed_meter(d, [0.0, 0.0], 0.0, dt);
|
||||
}
|
||||
@@ -805,7 +811,7 @@ impl SoundApp {
|
||||
{
|
||||
self.ensure_input();
|
||||
}
|
||||
if self.is_playing() || self.meters.values().any(|m| m.level[0] > 0.0001) || !self.pending_shots.is_empty() || !self.synthetic.is_empty() {
|
||||
if self.meters_live() || self.meters.values().any(|m| m.level[0] > 0.0001) || !self.pending_shots.is_empty() || !self.synthetic.is_empty() {
|
||||
ctx.request_repaint();
|
||||
}
|
||||
control::collect_screenshots(self, ctx);
|
||||
|
||||
@@ -22,11 +22,7 @@ pub fn show(app: &mut SoundApp, ui: &mut Ui) {
|
||||
pub(crate) fn content(app: &mut SoundApp, ui: &mut Ui) {
|
||||
let ids: Vec<TrackId> = app.engine.session().tracks.iter().filter(|x| !x.hidden && x.kind != TrackKind::Video).map(|x| x.id).collect();
|
||||
// Per-channel peaks for multichannel meters (read once per frame).
|
||||
let snap = app
|
||||
.player
|
||||
.as_ref()
|
||||
.filter(|_| app.engine.transport.playing || app.engine.session().tracks.iter().any(|t| t.mixer.input_monitor))
|
||||
.map(|p| p.meters());
|
||||
let snap = app.player.as_ref().filter(|_| app.meters_live()).map(|p| p.meters());
|
||||
egui::ScrollArea::horizontal().auto_shrink([false, false]).show(ui, |ui| {
|
||||
ui.horizontal_top(|ui| {
|
||||
ui.spacing_mut().item_spacing.x = 1.0;
|
||||
|
||||
Reference in New Issue
Block a user