# Never park an audio device in SUSPENDED.
#
# WHY (2026-07-26): WirePlumber suspends idle ALSA nodes to save power. Waking
# the device takes long enough that the attack of the first sound after silence
# is eaten — PLN heard the volume-feedback beep arrive faint and clipped
# ("muffled, like at 0.2"), while the on-screen volume UI reacted instantly.
#
# Harmless on a laptop; unacceptable on stage. The same mechanism would swallow
# the first hit of a track coming out of a quiet passage or a break — exactly
# the moments that have to land. Livecoding has long gaps where a stream is
# silent but must be ready to fire on the next cycle.
#
# session.suspend-timeout-seconds = 0 disables suspension entirely, so the
# device holds the stream open. Costs a little idle power; buys deterministic
# first-note timing.
#
# NARROWED 2026-08-03 — it used to match `~alsa_output.*` and `~alsa_input.*`,
# i.e. EVERY node in the graph.
#
# The reasoning at the time was "so it still holds if the default sink moves".
# The cost, measured during the J-5 dropout: six SOF sinks and a broken headset
# mic source were all held permanently open and inside the real-time graph,
# because never suspending is exactly what this file asks for. `pw-top` found
# HDMI1 burning 2.8ms of processing per cycle with no client attached, and the
# headset source at 1.4 MILLION xruns. A fix written to protect the UMC's first
# note was keeping every unused device awake and competing with it.
#
# THE LESSON, and it generalises: a rule written for one device and applied to
# the whole graph stops being a fix and becomes a load. Suspension is not the
# enemy — suspension OF THE DEVICE WE ARE PLAYING THROUGH is.
#
# Now scoped to the devices the rig actually performs on:
#   - the UMC202HD (the real output, and the one whose first-note attack was
#     being eaten in the first place)
#   - the internal Speaker, kept because it is the fallback that proves the
#     machine can still make sound when the UMC is in trouble
# Everything else may park, which is what we want: a parked device costs
# nothing and is not in the graph.
#
# Matched by NAME, not by card index — the UMC has been card 0 and card 1
# across replugs (see memory reference_umc202hd_interface).
#
# To revert: delete this file and `systemctl --user restart wireplumber`.

monitor.alsa.rules = [
  {
    matches = [
      { node.name = "~alsa_output.usb-BEHRINGER_UMC202HD.*" }
      { node.name = "~alsa_input.*U192k.*" }
      { node.name = "~alsa_input.usb-BEHRINGER_UMC202HD.*" }
      { node.name = "alsa_output.pci-0000_00_1f.3-platform-sof_sdw.HiFi__Speaker__sink" }
    ]
    actions = {
      update-props = {
        session.suspend-timeout-seconds = 0
      }
    }
  }
]
