Skip to content

Add opt-in experimental software volume for HDMI/DisplayPort - #119

Closed
Juns-g wants to merge 3 commits into
didriksg:mainfrom
Juns-g:feature/software-volume
Closed

Add opt-in experimental software volume for HDMI/DisplayPort#119
Juns-g wants to merge 3 commits into
didriksg:mainfrom
Juns-g:feature/software-volume

Conversation

@Juns-g

@Juns-g Juns-g commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

What & why

Add an opt-in, experimental software volume mode for external HDMI/DisplayPort outputs that do not expose usable hardware volume control. This uses public macOS 14.2+ Process Tap APIs without installing an audio driver; the app's minimum deployment target remains macOS 14.0.

Ready for review and maintainer merge consideration. The tester has confirmed the target-device core controls and accepted the final layout. CI passed on a4485fc5591ab9190357eac03c9cbd06bf026001, and the contributor has requested removing Draft status. Broader validation boundaries below remain disclosed; acceptance on this setup is not a claim of exhaustive device/OS coverage. No automatic merge is requested.

Included

  • Explicit consent in the display UI, initially off on every launch; enabling starts at 25% software gain (not a guaranteed safe loudness). The UI recommends working hardware volume control, labels the active hardware/software backend, and keeps a stop/mute warning beside the active software slider.
  • The software-volume toggle is placed below Hardware Volume Control (DDC). Its row follows Smooth scaling: a leading icon, title with explanation/status underneath, and a trailing switch. This last change is layout-only; consent, eligibility and audio behavior are unchanged.
  • Existing volume slider, mute and volume keys route through the app-owned software backend while active. DDC and forced DDC remain separate; software gain is not written back to hardware.
  • A device-scoped tap excludes Crisp's own live audio process and feeds a private capture/replay aggregate. Audio is processed locally, not recorded or uploaded.
  • Route/format changes, sleep and exit stop the session without automatic restart. Uncertain cleanup blocks another session instead of reporting successful recovery.
  • Audio capture usage text, English/Simplified Chinese UI strings, README, both website languages and build-path integration.
01-software-volume-off-layout 02-software-volume-consent-dialog 03-software-volume-active-slider

Limits and safety

  • First slice: current default HDMI/DisplayPort output, an unambiguous display/audio name match, one stereo 48 kHz Float32 virtual stream, and no hardware input streams. No all-device fallback or format conversion.
  • Disabling/stopping the engine, quitting, a crash, sleep or output change removes software attenuation: sound can suddenly become louder and software mute is lost. Set a comfortable monitor/TV hardware volume first. Crisp does not set hardware volume to 100%; software gain percentages are not TV remote levels. Working DDC or monitor/TV controls are preferred when available; a failed DDC probe never enables software mode automatically.
  • Multi-output/ambiguous-device setups, other formats, protected/exclusive playback and broader OS/device compatibility are not claimed as supported.

CLI follow-up (not included)

CLI audio-volume control is planned as a separate follow-up. Existing crispctl brightness commands do not control audio volume. No CLI commands or protocol changes are included here. Future CLI support should delegate to the same app-owned volume service and preserve explicit consent and lifecycle boundaries, rather than create another audio engine or silently enable capture. Command names and delivery timing are not finalized.

How tested

Hands-on user acceptance (2026-09-07)

  • Target setup: LG TV over HDMI with a 48 kHz output. The tester confirmed software attenuation/mute and the volume controls in the integrated test app. Volume keys worked after Accessibility permission was available and software mode was enabled again.
  • The final layout-only build was installed after normally quitting the previous app; installation files were byte-compared against the verified package and only the new app process was running. The tester then inspected the new layout and explicitly reported no remaining issue.
  • Accepted layout: icon/title/description/trailing switch, with Software Volume below Hardware Volume Control (DDC).
  • These are user-reported target-device results, not a claim that all permission, recovery or compatibility cases have been exercised. No new forced crash or audio playback was performed as part of the final layout installation.

Build and static evidence

  • Host make check was rerun successfully on the final layout changes: strict lint, unit tests and official localization-key validation (exit 0).
  • The final layout package passed targeted strict SwiftLint, a universal arm64/x86_64 Xcode Release build, built Chinese warning/capture-purpose checks, ZIP re-extraction checksums and deep/strict signature verification. It is ad-hoc signed and not notarized, not an official release.
  • The accepted package was built from 131bb14a5f8e7b3d6edb7da3845f5863269ead61 plus the layout-only patch included in this PR update. Its frozen source snapshot was verified unchanged during the build.
  • Local builds use the repository's existing Swift 5/minimal-concurrency policy. Default Xcode Swift 6 mode has unresolved concurrency diagnostics and is not claimed green. The full release-script packaging path has not been exercised locally.
  • Offline tests exercise gain/mute bounds, routing policy and actual interleaved DSP buffer clearing. Planar conversion still needs targeted coverage.
  • Earlier isolated speech experiments demonstrated attenuation/mute and audible restoration after controller failure. These do not establish full integrated-app crash recovery coverage.

Remaining review / validation boundaries

  • Target-device core volume controls and keyboard behavior confirmed by the tester.
  • Final layout inspected and accepted by the tester after installation.
  • Complete integrated-app permission deny/revoke and actual self-exclusion checks.
  • Complete DDC-isolation, route/disconnect, sleep and crash-recovery validation in the integrated app.
  • Attach a screenshot or clip of the final UI; the earlier screenshot predates the layout fix.
  • CI passed on final layout commit a4485fc5591ab9190357eac03c9cbd06bf026001: build run.

Accessibility-permission onboarding and a clearer reminder that software mode starts off after relaunch were noted as UX follow-ups; they are not implemented in this layout-only update.

Checklist

  • Local compile/tests and universal Xcode Release build with the existing Swift 5 build policy (not full release-script packaging).
  • Updated Crisp/Resources/Localizable.xcstrings for new user-facing strings.
  • Hands-on target-device feedback and final-layout acceptance recorded.
  • Final-layout screenshot/clip attached.

@Juns-g
Juns-g marked this pull request as ready for review September 6, 2026 17:31
@didriksg

didriksg commented Sep 6, 2026

Copy link
Copy Markdown
Owner

I've read the whole thing, including the LG run and the screenshots, and it's careful work: the consent step, the 25 percent start, the health check that stops the tap when the route or format changes, and refusing a second start when cleanup can't be verified are the right calls for what a software attenuator can and can't promise. But I'm not taking it into Crisp, at least not now.

Crisp's lane is talking to the monitor. Everything the volume row does today goes through the monitor's own controller over DDC, and the app never touches the audio stream. This changes that: it asks for the audio capture permission (the prompt says Crisp wants to record audio), keeps a tap running on the output, and its failure mode is the one you describe yourself, the attenuation vanishing on a quit, a crash, a sleep or an output change with the sound jumping to wherever the monitor sits. You handled each of those as well as it can be handled; it's the approach I don't want in a display app. It also puts a permanent row with a paragraph under it on every external display's card, a label over every volume slider, and a dialog in the panel, and the panel is meant to stay free of all three. For the monitors where DDC volume is dead, it stays the monitor's buttons or a dedicated audio app for now.

I'm closing this, and I've put the idea on the roadmap in Backlog so it isn't lost; if it moves, this PR is where the work starts from.

@didriksg didriksg closed this Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants