Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/desktop-gpui/src/assets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ const ICONS: &[(&str, &[u8])] = assets!("icons":
"ratio.svg",
"refresh-cw.svg",
"rotate-ccw.svg",
"volume-2.svg",
"volume-x.svg",
"gem.svg",
"sliders-horizontal.svg",
Expand Down
72 changes: 72 additions & 0 deletions apps/desktop-gpui/src/editor_window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ impl EditorVerticalLayout {
}
}

/// Length of each audio-scrub slice; the next one replaces what is left.
const SCRUB_SLICE_SECS: f64 = 0.09;
const PRESETS_MENU_WIDTH: f32 = 288.;
const PRESET_SUBMENU_WIDTH: f32 = 208.;
const PRESET_SUBMENU_HEIGHT: f32 = 5. * 32. + 4. * 2. + 8.;
Expand Down Expand Up @@ -1367,6 +1369,12 @@ pub struct EditorWindow {
/// Kept alive for the window's lifetime: dropping the last `Arc` is what
/// tears the decoders down, and `dispose()` on close does it explicitly.
pub(crate) instance: Option<Arc<EditorInstance>>,
/// Audio scrub: a slice request is in flight, and the newest position
/// asked for meanwhile (played once it lands).
/// The toolbar's speaker toggle ([`crate::store::audio_scrub_enabled`]).
audio_scrub: bool,
scrub_audio_busy: bool,
scrub_audio_next: Option<f64>,
focus: FocusHandle,

// -- Transport ----------------------------------------------------------
Expand Down Expand Up @@ -2070,6 +2078,9 @@ impl EditorWindow {
total: 0.0,
transport: None,
scrub: None,
audio_scrub: crate::store::audio_scrub_enabled(),
scrub_audio_busy: false,
scrub_audio_next: None,
stats: None,
play_mark: None,
timeline: TimelineModel::default(),
Expand Down Expand Up @@ -3989,6 +4000,7 @@ impl EditorWindow {
// `applyScrub()` runs immediately on mousedown.
self.scrub = Some(Scrub::Ruler);
self.seek_to_time(time, cx);
self.scrub_audio_at(time, cx);
} else {
self.scrub = Some(Scrub::Press { time });
}
Expand Down Expand Up @@ -4017,6 +4029,41 @@ impl EditorWindow {
// in-flight work when it sees a newer instruction
// (`editor_instance.rs:563-575`).
self.scrub_to_time(time, cx);
self.scrub_audio_at(time, cx);
}

/// Audio scrub: while the ruler is dragged with playback stopped, play a
/// short slice of the mix under the playhead. One request at a time; the
/// newest position waits and plays next, spaced by about a slice so they
/// run back to back.
fn scrub_audio_at(&mut self, time: f64, cx: &mut Context<Self>) {
if self.playing || !self.audio_scrub {
return;
}
if self.scrub_audio_busy {
self.scrub_audio_next = Some(time);
return;
}
let Some(instance) = self.instance.clone() else {
return;
};
self.scrub_audio_busy = true;
let slice = gpui_tokio::Tokio::spawn(cx, async move {
instance.scrub_audio(time, SCRUB_SLICE_SECS).await;
});
cx.spawn(async move |this, cx| {
let _ = slice.await;
cx.background_executor()
.timer(Duration::from_millis(60))
.await;
let _ = this.update(cx, |this, cx| {
this.scrub_audio_busy = false;
if let Some(next) = this.scrub_audio_next.take() {
this.scrub_audio_at(next, cx);
}
});
})
.detach();
}

/// The **timeline's own** mouseup, the one the source registers on
Expand Down Expand Up @@ -10538,6 +10585,31 @@ impl EditorWindow {
)),
),
)
.child(
ui::EditorButton::plain(&theme, "transport-audio-scrub")
.left_icon(if self.audio_scrub {
"icons/volume-2.svg"
} else {
"icons/volume-x.svg"
})
.tooltip(
&theme,
if self.audio_scrub {
"Audio while scrubbing: on"
} else {
"Audio while scrubbing: off"
},
)
.pressed(self.audio_scrub)
.on_click(cx.listener(|this, _, window, cx| {
this.audio_scrub = !this.audio_scrub;
if !crate::store::set_audio_scrub_enabled(this.audio_scrub) {
tracing::warn!("could not save the audio scrub preference");
}
cx.notify();
window.refresh();
})),
)
.child(self.header_divider())
// `IconCapZoomOut` -> `updateZoom(zoom * 1.1,
// playbackTime)`; `IconCapZoomIn` -> `zoom / 1.1`
Expand Down
14 changes: 14 additions & 0 deletions apps/desktop-gpui/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1096,6 +1096,20 @@ pub fn set_studio_sound_defaults(enabled: bool, isolation: cap_project::VoiceIso
section.insert("isolation".into(), serde_json::to_value(isolation).unwrap());
set_store_value(AUDIO_ENHANCEMENT, Value::Object(section))
}
/// Editor preferences that belong to the user rather than a project.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Comments repeat the code

The new comment above EDITOR_PREFERENCES only restates the constant's purpose. AGENTS.md requires comments to explain non-obvious context and explicitly bans narration of the code. Remove this comment and the similar narration above audio_scrub_enabled and SCRUB_SLICE_SECS; the pattern also appears elsewhere in the additions. This repository requirement must be satisfied before merging.

Context Used: AGENTS.md (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop-gpui/src/store.rs
Line: 1099

Comment:
**Comments repeat the code**

The new comment above `EDITOR_PREFERENCES` only restates the constant's purpose. `AGENTS.md` requires comments to explain non-obvious context and explicitly bans narration of the code. Remove this comment and the similar narration above `audio_scrub_enabled` and `SCRUB_SLICE_SECS`; the pattern also appears elsewhere in the additions. This repository requirement must be satisfied before merging.

**Context Used:** AGENTS.md ([source](https://github.com/capsoftware/cap/blob/main/AGENTS.md))

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

pub const EDITOR_PREFERENCES: &str = "editor_preferences";
pub const AUDIO_SCRUB_KEY: &str = "audioScrub";

/// Whether dragging the playhead on the ruler plays the audio under it.
/// Defaults on; the timeline toolbar's speaker toggle writes it.
pub fn audio_scrub_enabled() -> bool {
bool_at(&store_section(EDITOR_PREFERENCES), AUDIO_SCRUB_KEY, true)
}

pub fn set_audio_scrub_enabled(enabled: bool) -> bool {
set_store_setting(EDITOR_PREFERENCES, AUDIO_SCRUB_KEY, Value::Bool(enabled))
}

/// `RecordingSettingsStore::KEY` (`src-tauri/src/recording_settings.rs:37`) --
/// the section the tray's Select Mode submenu reads and writes.
pub const RECORDING_SETTINGS: &str = "recording_settings";
Expand Down
Loading
Loading