Skip to content

Instantly share code, notes, and snippets.

@wyf9
Last active May 1, 2026 16:23
Show Gist options
  • Select an option

  • Save wyf9/ff12240ae023da0a068f2466968e3681 to your computer and use it in GitHub Desktop.

Select an option

Save wyf9/ff12240ae023da0a068f2466968e3681 to your computer and use it in GitHub Desktop.
Virtual Sink/Mic Script for PipeWire
#!/usr/bin/env bash
set -euo pipefail
LOCKDIR="/tmp/viraudio"
mkdir -p "$LOCKDIR"
command -v pactl >/dev/null || { echo "Missing pactl, please install it first." >&2; exit 1; }
usage() {
cat <<EOF
Usage:
$0 sink [NAME]
$0 mic [NAME]
$0 del <NAME>
$0 rm <NAME>
$0 rm --all
$0 rm --all --sink
$0 rm --all --mic
Source: https://gist.github.com/wyf9/ff12240ae023da0a068f2466968e3681
Help: https://wyf9.top/p/virtual-sink#usage
EOF
exit 1
}
die() {
echo "$*" >&2
exit 1
}
lock_file() {
local type="$1"
local name="$2"
printf '%s/viraudio-%s-%s-%s.lock' "$LOCKDIR" "$USER" "$name" "$type"
}
exists_name() {
local name="$1"
[[ -f "$(lock_file sink "$name")" || -f "$(lock_file mic "$name")" ]]
}
generate_name() {
local type="$1"
local prefix
case "$type" in
sink) prefix="Virtual Sink" ;;
mic) prefix="Virtual Mic" ;;
esac
local i=1
while exists_name "$prefix $i"; do
((i++))
done
echo "$prefix $i"
}
create_sink() {
local name="${1:-}"
if [[ -z "$name" ]]; then
name=$(generate_name sink)
fi
if exists_name "$name"; then
die "Virtual device '$name' already exists."
fi
local module_index
module_index=$(
pactl load-module module-null-sink \
media.class=Audio/Sink \
sink_name="$name" \
sink_properties=device.description="$name"
)
printf '%s\n' "$module_index" > "$(lock_file sink "$name")"
echo "Created virtual sink '$name' (module $module_index)."
}
create_mic() {
local name="${1:-}"
if [[ -z "$name" ]]; then
name=$(generate_name mic)
fi
if exists_name "$name"; then
die "Virtual device '$name' already exists."
fi
local module_index
module_index=$(
pactl load-module module-null-sink \
media.class=Audio/Source/Virtual \
sink_name="$name" \
sink_properties=device.description="$name"
)
printf '%s\n' "$module_index" > "$(lock_file mic "$name")"
echo "Created virtual mic '$name' (module $module_index)."
}
remove_name() {
local name="$1"
local removed=0
for type in sink mic; do
local lf
lf="$(lock_file "$type" "$name")"
if [[ -f "$lf" ]]; then
local module_index
module_index="$(<"$lf")"
pactl unload-module "$module_index"
rm -f "$lf"
echo "Removed virtual $type '$name' (module $module_index)."
removed=1
fi
done
if [[ $removed -eq 0 ]]; then
die "No virtual sink or mic named '$name' found."
fi
}
remove_all() {
local type_filter="${1:-}"
local types=()
local removed=0
if [[ -z "$type_filter" ]]; then
types=(sink mic)
else
types=("$type_filter")
fi
for type in "${types[@]}"; do
for lf in "$LOCKDIR"/viraudio-*"-$type".lock; do
if [[ -f "$lf" ]]; then
local module_index
module_index="$(<"$lf")"
local name
name=$(basename "$lf" "-$type.lock" | sed "s/^viraudio-[^-]*-//")
pactl unload-module "$module_index"
rm -f "$lf"
echo "Removed virtual $type '$name' (module $module_index)."
removed=$((removed + 1))
fi
done
done
if [[ $removed -eq 0 ]]; then
die "No virtual devices found."
fi
echo "Total removed: $removed."
}
if [[ $# -lt 1 ]]; then
usage
fi
cmd="$1"
shift
name="$*"
case "$cmd" in
sink)
create_sink "$name"
;;
mic)
create_mic "$name"
;;
del|rm)
if [[ "$name" == "--all" ]]; then
remove_all
elif [[ "$name" == "--all --sink" ]] || [[ "$name" == "--sink --all" ]]; then
remove_all sink
elif [[ "$name" == "--all --mic" ]] || [[ "$name" == "--mic --all" ]]; then
remove_all mic
elif [[ -z "$name" ]]; then
die "NAME required for del/rm, or use --all."
else
remove_name "$name"
fi
;;
*)
usage
;;
esac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment