fix frame interval
This commit is contained in:
42
src/app.rs
42
src/app.rs
@@ -40,6 +40,9 @@ pub struct App {
|
||||
chains: Vec<EffectChain>, // one per deck, in order
|
||||
osc_rx: crossbeam_channel::Receiver<OscAppCmd>,
|
||||
audio_dir: PathBuf,
|
||||
|
||||
/// Track if we need to redraw the UI
|
||||
dirty: bool,
|
||||
}
|
||||
|
||||
impl App {
|
||||
@@ -91,6 +94,7 @@ impl App {
|
||||
chains,
|
||||
osc_rx,
|
||||
audio_dir,
|
||||
dirty: true,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -135,12 +139,36 @@ impl App {
|
||||
&mut self,
|
||||
terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,
|
||||
) -> Result<()> {
|
||||
loop {
|
||||
terminal.draw(|f| tui::draw(f, self))?;
|
||||
// Track if any deck is playing to determine frame rate
|
||||
let mut playing = false;
|
||||
let mut last_frame_time = std::time::Instant::now();
|
||||
|
||||
// Drain audio status
|
||||
loop {
|
||||
// Check if any deck is playing
|
||||
playing = self.decks.iter().any(|d| d.play_state == PlayState::Playing);
|
||||
|
||||
// Use adaptive frame rate: 60fps when playing, 30fps when idle
|
||||
let frame_interval = if playing {
|
||||
Duration::from_millis(16) // ~60fps
|
||||
} else {
|
||||
Duration::from_millis(33) // ~30fps
|
||||
};
|
||||
|
||||
// Only redraw if dirty or if we're at the right time
|
||||
if self.dirty || last_frame_time.elapsed() >= frame_interval {
|
||||
terminal.draw(|f| tui::draw(f, self))?;
|
||||
self.dirty = false;
|
||||
last_frame_time = std::time::Instant::now();
|
||||
}
|
||||
|
||||
// Drain audio status (limit to 100 messages per frame to prevent spinning)
|
||||
let mut status_count = 0;
|
||||
while let Ok(status) = self.audio.status_rx.try_recv() {
|
||||
self.handle_audio_status(status);
|
||||
status_count += 1;
|
||||
if status_count >= 100 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Drain OSC commands
|
||||
@@ -148,8 +176,8 @@ impl App {
|
||||
if self.handle_osc_cmd(cmd)? { return Ok(()); }
|
||||
}
|
||||
|
||||
// Keyboard (16ms poll → ~60fps)
|
||||
if event::poll(Duration::from_millis(16))? {
|
||||
// Keyboard (use adaptive poll interval)
|
||||
if event::poll(frame_interval)? {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
if self.handle_key(key)? { return Ok(()); }
|
||||
}
|
||||
@@ -186,6 +214,7 @@ impl App {
|
||||
}
|
||||
AudioStatus::Position { deck, position } => {
|
||||
self.decks[deck as usize].position = position;
|
||||
self.dirty = true;
|
||||
osc::send_osc(
|
||||
&format!("/deck/{}/position", deck as usize),
|
||||
vec![rosc::OscType::Float(
|
||||
@@ -197,6 +226,7 @@ impl App {
|
||||
let d = &mut self.decks[deck as usize];
|
||||
d.vu.0 = (d.vu.0 * 0.65).max(l);
|
||||
d.vu.1 = (d.vu.1 * 0.65).max(r);
|
||||
self.dirty = true;
|
||||
osc::send_osc(
|
||||
&format!("/deck/{}/vu", deck as usize),
|
||||
vec![rosc::OscType::Float(l), rosc::OscType::Float(r)],
|
||||
@@ -204,10 +234,12 @@ impl App {
|
||||
}
|
||||
AudioStatus::TrackEnded(deck) => {
|
||||
self.decks[deck as usize].play_state = PlayState::Stopped;
|
||||
self.dirty = true;
|
||||
}
|
||||
AudioStatus::Error { deck, msg } => {
|
||||
tracing::error!("Deck {:?}: {}", deck, msg);
|
||||
self.decks[deck as usize].track_name = Some(format!("ERR: {}", msg));
|
||||
self.dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
src/audio.rs
11
src/audio.rs
@@ -79,6 +79,9 @@ struct DeckState {
|
||||
sample_rate: u32,
|
||||
read_pos: usize, // byte index into pcm (always even)
|
||||
cue_pos: usize,
|
||||
|
||||
/// Track last VU update time to rate-limit updates
|
||||
last_vu_update: std::time::Instant,
|
||||
}
|
||||
|
||||
impl DeckState {
|
||||
@@ -90,6 +93,7 @@ impl DeckState {
|
||||
sample_rate: 44100,
|
||||
read_pos: 0,
|
||||
cue_pos: 0,
|
||||
last_vu_update: std::time::Instant::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -220,11 +224,18 @@ fn engine_thread(cmd_rx: Receiver<AudioCmd>, status_tx: Sender<AudioStatus>) ->
|
||||
deck: ds.id,
|
||||
position: pos,
|
||||
});
|
||||
|
||||
// Rate-limit VU updates to ~60Hz (16ms) to reduce CPU usage
|
||||
// while still providing smooth updates during playback
|
||||
let now = std::time::Instant::now();
|
||||
if now.duration_since(ds.last_vu_update) >= std::time::Duration::from_millis(16) {
|
||||
let _ = status_tx_cb.try_send(AudioStatus::Vu {
|
||||
deck: ds.id,
|
||||
l: vu_l,
|
||||
r: vu_r,
|
||||
});
|
||||
ds.last_vu_update = now;
|
||||
}
|
||||
}
|
||||
})
|
||||
.register()?; // Connect the stream with F32LE stereo format
|
||||
|
||||
@@ -186,7 +186,7 @@ impl EffectChain {
|
||||
value = value,
|
||||
);
|
||||
|
||||
tracing::debug!(
|
||||
tracing::info!(
|
||||
"set-param deck {} node {} Props {}",
|
||||
self.deck.label(),
|
||||
node_id,
|
||||
|
||||
13
src/tui.rs
13
src/tui.rs
@@ -109,7 +109,7 @@ fn draw_deck(f: &mut Frame, deck: &Deck, area: Rect, focused: bool) {
|
||||
.borders(Borders::ALL)
|
||||
.title(title)
|
||||
.border_style(border_style)
|
||||
.style(Style::default().bg(Color::Indexed(0)));
|
||||
.style(Style::default());
|
||||
f.render_widget(outer, area);
|
||||
|
||||
let inner = area.inner(ratatui::layout::Margin { horizontal: 1, vertical: 1 });
|
||||
@@ -142,7 +142,7 @@ fn draw_deck(f: &mut Frame, deck: &Deck, area: Rect, focused: bool) {
|
||||
// Progress
|
||||
f.render_widget(
|
||||
Gauge::default()
|
||||
.gauge_style(Style::default().fg(color).bg(Color::Indexed(0)))
|
||||
.gauge_style(Style::default().fg(color))
|
||||
.percent((deck.progress() * 100.0) as u16)
|
||||
.label(""),
|
||||
rows[1],
|
||||
@@ -153,8 +153,7 @@ fn draw_deck(f: &mut Frame, deck: &Deck, area: Rect, focused: bool) {
|
||||
f.render_widget(
|
||||
Gauge::default()
|
||||
.gauge_style(Style::default()
|
||||
.fg(if fader_pct > 10 { Color::White } else { Color::DarkGray })
|
||||
.bg(Color::Indexed(0)))
|
||||
.fg(if fader_pct > 10 { Color::White } else { Color::DarkGray }))
|
||||
.percent(fader_pct.min(100))
|
||||
.label(format!("VOL {:3}%", fader_pct)),
|
||||
rows[3],
|
||||
@@ -191,8 +190,8 @@ fn draw_vu(f: &mut Frame, vu: (f32, f32), area: Rect, color: Color) {
|
||||
let lp = (vu.0 * 100.0) as u16;
|
||||
let rp = (vu.1 * 100.0) as u16;
|
||||
|
||||
f.render_widget(Gauge::default().gauge_style(Style::default().fg(vc(lp)).bg(Color::Indexed(0))).percent(lp.min(100)).label("L"), la);
|
||||
f.render_widget(Gauge::default().gauge_style(Style::default().fg(vc(rp)).bg(Color::Indexed(0))).percent(rp.min(100)).label("R"), ra);
|
||||
f.render_widget(Gauge::default().gauge_style(Style::default().fg(vc(lp))).percent(lp.min(100)).label("L"), la);
|
||||
f.render_widget(Gauge::default().gauge_style(Style::default().fg(vc(rp))).percent(rp.min(100)).label("R"), ra);
|
||||
}
|
||||
|
||||
fn draw_help(f: &mut Frame, area: Rect) {
|
||||
@@ -225,7 +224,7 @@ fn draw_help(f: &mut Frame, area: Rect) {
|
||||
.block(Block::default()
|
||||
.borders(Borders::TOP)
|
||||
.border_style(Style::default().fg(Color::DarkGray)))
|
||||
.style(Style::default().bg(Color::Indexed(0)));
|
||||
.style(Style::default());
|
||||
|
||||
f.render_widget(help, area);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user