Skip to content

feat(desktop-gpui): per-clip Voice EQ with de-click - #3547

Open
richiemcilroy wants to merge 8 commits into
mainfrom
feat/voice-eq
Open

richiemcilroy wants to merge 8 commits into
mainfrom
feat/voice-eq

Conversation

@richiemcilroy

@richiemcilroy richiemcilroy commented Oct 29, 2026 •

Copy link
Copy Markdown
Member

Adds Voice EQ to the desktop editor. It sets the microphone's tone clip by clip, non-destructively. It has five presets, a draggable five-band curve (low cut, low shelf, two bells, high shelf) and optional De-click for mouth clicks and crackle. Playback and export run the same renderer, so what you hear while editing is what exports.

Before After
before after
before audio tab after audio tab
One clip on its own Band controls and de-click Clip menu
per clip bands clip menu
Linux Windows
linux windows

How it works

flowchart LR
    mic[Mic track] --> tone["VoiceTone<br/>EQ cascade + de-click"]
    tone --> studio["Studio Sound<br/>(if on)"]
    studio --> mix[Mixer]
    system[System audio] --> mix
    mix --> play[Preview playback]
    mix --> exp[Export]
Loading
  • Per clip, stored in the project. TimelineSegment gains an optional voice_eq (crates/project/src/configuration.rs:1166), so existing projects load unchanged. VoiceEq (configuration.rs:766) holds the preset, the low cut, four bands and de-click. Turning the toggle off keeps the settings, so you can A/B. Values are sanitized and clamped on read, so a hand-edited project can't push the filters out of range.
  • Engine (crates/audio/src/voice_tone.rs).
    • RBJ biquads computed in f64. The low cut is a 4th-order Butterworth.
    • Every 24,000-frame block is rendered from a fresh filter state primed over a fixed pre-roll on an absolute sample grid (voice_tone.rs:261). A sample's value therefore never depends on where playback, a seek or an export started, which is what keeps preview and export identical.
    • All sections run in one pass per sample (voice_tone.rs:240), which is bit-identical to filtering one section at a time and about 2x faster.
    • De-click detects clicks from the forward and backward prediction error of a local AR(16) model, measured against the 98th percentile nearby. Voiced speech sets the bar, so glottal pulses don't trigger it. Each detected gap is rebuilt by least-squares AR interpolation (voice_tone.rs:479).
  • Wiring.
    • segment_voice_tone (crates/editor/src/audio.rs:922) turns a clip's settings into a VoiceToneSettings, or None when the result would be untouched audio. With no Voice EQ, the audio path is exactly what it was.
    • Playback runs the tone stage ahead of Studio Sound, inside the existing voice enhancement cache.
    • Export does the same per track (crates/editor/src/export_audio.rs:485), with enough lookbehind that its block edges land on the same grid.
    • Clips with Voice EQ skip the "preparing audio" fast path (preparing_audio/mixer.rs:78), because that path doesn't run the tone stage.
  • UI (apps/desktop-gpui/src/editor_voice_eq.rs).
    • A card in the Audio tab with presets and a curve. Drag a point to set frequency and gain, scroll over it to change Q, and double-click to reset it.
    • Below the curve are a band picker with exact sliders and the de-click control.
    • With no clip selected, edits apply to every clip. Selecting clips scopes the edit to them, and Apply to all clips copies their settings to the rest.
    • The curve uses the engine's own response function (ToneResponse), so the plot is the filter that actually runs.
    • Clips show a "Voice EQ" badge on the timeline (editor_timeline.rs:3575). The clip menu gets a Voice EQ row with a summary such as "Clear · De-click", which opens the card scoped to that clip.
    • Strings are translated in all seven locales, and the Studio Mode docs gain a "Shape your microphone sound" section.
// project-config.json, timeline.segments[n]
"voiceEq": {
  "enabled": true, "preset": "clear",
  "highPass": true, "highPassHz": 90,
  "low":     { "hz": 150,   "gainDb": -1.5, "q": 0.7 },
  "lowMid":  { "hz": 320,   "gainDb": -3,   "q": 1.2 },
  "highMid": { "hz": 3500,  "gainDb": 3.5,  "q": 0.9 },
  "high":    { "hz": 10000, "gainDb": 2,    "q": 0.7 },
  "declick": "light"
}

Cost

Release build on an M4 Mac mini, rendering 10 minutes of synthetic voice at 48 kHz. Clips without Voice EQ cost nothing extra, because the stage isn't created.

Setting Time for 10 min of audio Share of one core in real time Cold seek
EQ, mono 250 ms 0.04% 0.2 ms
EQ, stereo 505 ms 0.08% 0.4 ms
EQ + strong de-click 1.75 s 0.29% 1.5 ms

Playback renders on the existing prerender thread in cancellable chunks, not in the device callback, and export audio runs on its own worker in parallel with video.

RetriggerView in GreptileConfidence Score: 5/5

The changes since the last review appear safe to merge, with no new actionable findings.

Summary

Adds per-clip Voice EQ and de-click, with shared audio rendering for preview and export.

  • The editor lets people shape microphone sound separately on each timeline clip.
  • Playback and export apply the same Voice EQ and de-click to microphone audio.

Reviews (2) · Last reviewed commit: "chore(project): trim the VoiceEq comment..." · Reviewed by Greptile

@vercel

vercel Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
cap-status Ignored Ignored Preview Oct 29, 2026 5:17pm UTC

Comment thread apps/desktop-gpui/src/editor_edits.rs Outdated

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Capy found no issues.

View 2 other findings in Capy.

Open in Capy

@richiemcilroy

Copy link
Copy Markdown
Member Author

hey @greptileai, please re-review the PR

@capy-ai capy-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Capy found no issues.

Open in Capy

This branch was successfully deployed

1 active (outdated) deployment
Preview – cap-web — 4c3631ab Deployed Oct 29, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant