Skip to content

Meter the Mac microphone like a stock one (omarchy-mac 0.1.3) - #28

Merged
maralcbr merged 4 commits into
mainfrom
mac-mic-typed-source
Oct 10, 2026
Merged

maralcbr merged 4 commits into
mainfrom
mac-mic-typed-source

Conversation

@maralcbr

Copy link
Copy Markdown
Collaborator

Summary

The Mac microphone now reaches the audio panel the same way a stock microphone does: the panel's normal path handles volume, mute and the input level meter. Nothing in the runtime changes.

 effect_output.jNNN-mic            (asahi-audio DSP, mono AUX0)
-  ─FL,FR→ omarchy_asahi_mic       module-null-sink, Audio/Source/Virtual
-                                  → panel: untyped, wpctl volume, no meter
+  ─FL (WirePlumber), FR (mapper)→ input.omarchy_asahi_mic
+                                  module-remap-source capture stream (hidden in audio.json)
+     → omarchy_asahi_mic          Audio/Source [FL FR], "MacBook Microphone"
+                                  → panel: typed, PwNodeAudio + PwNodePeakMonitor
  • Mapper (omarchy-audio-asahi-mic-map): loads module-remap-source master=<dsp> in place of the virtual null sink. WirePlumber links the mono port to one input only (si-standard-link gives each output port one best input), so the mapper waits for its FL link and then adds FR. It adds FL as well only if WirePlumber never makes it. A link WirePlumber left in error is replaced. Kept as before: the node name, the description, the selection rules, the #505 sample, the #99 untargeted-route check, outage handling and gain persistence.
  • Upgrade: retire_virtual() removes 0.1.2's virtual source with the same name and keeps its gain, mute and selection. Only a mapper restarted inside a session meets one; a new session's pipewire-pulse starts with no modules, and the saved state carries the gain.
  • audio.json hides input.omarchy_asahi_mic, which would otherwise count as an app recording in the bar's mic widget.
  • Removes omarchy-audio-asahi-mic-level. It belonged to the panel meter that was dropped from Converge omarchy-mac and omarchy-mx-mac into upstream Omarchy omarchy#14431, and nothing calls it.
  • Headset Microphone: no change. The runtime panel lists every typed source whatever its port availability; sourceAvailability only admits untyped sources. A stock UCM laptop with a separate headset node shows the same entry.
  • Bumps omarchy-mac to 0.1.3.

Evidence

Tested on the M2 Max (j416; pipewire 1.6.9, wireplumber 0.5.18, Quickshell 0.3.1, runtime e1b0e5e9b) with this branch's package (pacman -U omarchy-mac-0.1.3-1, source-revision 89f6c16).

  • Before: omarchy_asahi_mic is Audio/Source/Virtual. With the audio panel open there is no peak stream (untyped). When the default was the DSP, the shell logged PwNode(id=64) is missing channels present in capture stream about 47 times a second.
    After: pw-cli info: media.class = "Audio/Source", audio.position = [ FL, FR ], default source omarchy_asahi_mic. The open panel's "Quickshell Peak Detect" stream is linked omarchy_asahi_mic:capture_FL/FR → quickshell [active], with no peak errors.
  • Untargeted parec (#99 case) is fed by {omarchy_asahi_mic} with signal on both channels (peak 0.0049 L/R). parecord with no device: peak 132/132 (s16).
  • wpctl set-volume <mic> 0.2 scales the recorded peak to 0.8%, set-mute 1 gives exact zeros, and the gain is saved to asahi-mic-gain.json.
  • Upgrade: put the M2 back on 0.1.2 (virtual source, 70% gain, selected), ran pacman -U, then restarted the unit. Result: one "MacBook Microphone", still the default, 70% kept, the open panel's peak monitor follows it, the level helper is gone.
  • Mic card off then on (shorter than the notice grace): the capture stream is left unlinked during the outage (no stray feed). On return both links come back and the signal is stereo. The module was unloaded 3× while the panel was open; each time it was rebuilt, re-selected and followed by the peak monitor.
  • A deleted WirePlumber FL link is restored by the mapper on the next event, and WirePlumber logs no errors.
  • The default sink is still audio_effect.j416-convolver, so speakers and headphones are untouched.
  • No panel screenshot: the Mac sat at the lock screen during testing.
  • Suites, as CI runs them (Docker, Arch Linux ARM, runtime e1b0e5e9b): scope and architecture gates, omarchy-mac/test/all, omarchy-mac-boot/test/all and test/integration/all all pass. New mic tests cover the remap load, waiting for WirePlumber's link, the race with it, replacing a failed link, and the 0.1.2 migration. The handoff test fails when the DSP handoff is removed.

Merge Danger

Door: two-way

Rolling back to 0.1.2 is safe: its mapper reports the remap source as "a source this service does not own" until the next login, when pipewire-pulse starts empty and it builds its virtual source again. The saved gain format is unchanged.

Blast Radius: Apple-Silicon-microphone

Only Apple Silicon runs any of this: the mapper and unit are gated on omarchy-hw-apple-silicon, and x86 is untouched. Apps that record from the default or from "MacBook Microphone" keep the same node name. In wpctl status the mic now appears under Filters (loopback group), like asahi-audio's speaker node.

The default input was a null sink typed Audio/Source/Virtual. The desktop
shell types only exact media classes, so it drove that source's volume and
mute through wpctl and showed no level meter for it; its peak monitor on
asahi-audio's mono DSP source drops every buffer. The microphone never
behaved like a stock one in the audio panel.

The mapper now loads pipewire-pulse's module-remap-source instead: a
loopback whose capture stream records the DSP source and whose other side
is an Audio/Source with front-left and front-right, named and described
as before. WirePlumber links the mono port to the left side only, so the
mapper waits for that link and adds the right one. The panel then drives
volume, mute and the meter through its normal typed path.

On its first pass after an upgrade the mapper replaces 0.1.2's virtual
source, keeping its gain, mute and selection. The audio hints hide the
remap's own capture stream, which would otherwise count as an app
recording.
omarchy-audio-asahi-mic-level recorded the virtual mapping for a panel
meter that never landed upstream. The typed microphone source is metered
by the panel itself, so the helper and its test go.
pw-link reports "File exists" both when WirePlumber linked the same ports
meanwhile and when a link it left in error still holds them. Only the
first is a working link; the second carried nothing and, with the source
already built, was never rebuilt. The mapper now deletes a failed link and
links the ports itself. The upgrade test also drops the default to a
monitor when the 0.1.2 source goes, as PipeWire does, so it checks the
handoff that reselects the microphone.

@scottjones scottjones left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks Marcelo, this is a really nice change. Getting the mic onto the panel's normal path, meter included, is exactly what it needed.

I tested it on my 14" MacBook Pro M2 Max (J414c) with omarchy-mac 0.1.0-10, asahi-audio 4.1, PipeWire 1.6.9 and WirePlumber 0.5.17. I ran the mapper once from a checkout at 89f6c16 (no --watch):

  • It converted my existing null-sink mapping, and "MacBook Microphone" came up as an Audio/Source.
  • The internal mic PCM (card0, AppleJ414HPAI pcm0c) stayed closed for 30 s at idle.
  • pw-record made it RUNNING, and it closed 6 s after the recording stopped.
  • The panel's level meter moved with speech, and the PCM closed 3 s after I closed the panel.

So there's no idle cost, which matches my reading that the remap source's capture link is passive.

One small thing, not blocking: if I remove the WirePlumber FL wait in the mapper (omarchy-audio-asahi-mic-map lines 551-555), mic-test.sh still passes. The delays at mic-test.sh:344 are (0, 3), which are too short to need the wait. Adding a delay of 8 fails without the wait and passes with it.

The version bump can come out once #30 lands, since the release will set it. No need to hold this one for that.

@maralcbr
maralcbr merged commit d8100ec into main Oct 10, 2026
6 checks passed
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