2026-09-19 17:22:03 +08:00
|
|
|
#!/bin/bash
|
2026-09-19 18:06:23 +08:00
|
|
|
# Collaplex 音效 — 把链路固化装到 PipeWire(用户级)
|
2026-09-19 17:22:03 +08:00
|
|
|
#
|
2026-09-19 18:16:43 +08:00
|
|
|
# 链路: 全局音频(应用输出) -> collaplex_vsink(虚拟声卡)
|
|
|
|
|
# -> 响度归一化(pipe DSP) -> collaplex_hrtf_in(双耳 HRTF + 房间混响) -> 数字输出
|
2026-09-19 17:22:03 +08:00
|
|
|
#
|
2026-09-19 18:16:43 +08:00
|
|
|
# 装什么:
|
|
|
|
|
# $HOME/.local/bin/collaplex-loudness-norm pipe 插件入口(指向本项目 dsp/)
|
|
|
|
|
# $HOME/.config/pipewire/pipewire.conf.d/10-...conf 虚拟声卡 + 响度归一化
|
|
|
|
|
# $HOME/.config/pipewire/pipewire.conf.d/20-...conf HRTF 核心(接入 + 输出)
|
|
|
|
|
#
|
|
|
|
|
# 湿量: WET=0.3 环境变量覆盖(默认 0.3)
|
2026-09-19 17:22:03 +08:00
|
|
|
set -eu
|
|
|
|
|
|
|
|
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
2026-09-19 18:06:23 +08:00
|
|
|
CONF_DIR="$HOME/.config/pipewire/pipewire.conf.d"
|
2026-09-19 18:16:43 +08:00
|
|
|
WRAPPER="$HOME/.local/bin/collaplex-loudness-norm"
|
2026-09-19 18:09:29 +08:00
|
|
|
WET="${WET:-0.3}"
|
2026-09-19 18:24:18 +08:00
|
|
|
VOL_DB="${VOL_DB:-6}"
|
|
|
|
|
VOL="$(/usr/bin/python3 -c "print(f'{10**(${VOL_DB}/20):.4f}')")"
|
2026-09-19 18:06:23 +08:00
|
|
|
HRTF_IN="collaplex_hrtf_in"
|
2026-09-19 18:34:56 +08:00
|
|
|
EQ_IN="collaplex_eq_in"
|
2026-09-19 17:22:03 +08:00
|
|
|
|
2026-09-19 18:16:43 +08:00
|
|
|
# ---- 1. 探测数字输出 ----
|
|
|
|
|
TARGET=$(pw-dump | /usr/bin/python3 -c '
|
|
|
|
|
import json, sys
|
2026-09-19 18:09:29 +08:00
|
|
|
for n in json.load(sys.stdin):
|
2026-09-19 18:16:43 +08:00
|
|
|
if n.get("type") != "PipeWire:Interface:Node":
|
2026-09-19 18:06:23 +08:00
|
|
|
continue
|
2026-09-19 18:16:43 +08:00
|
|
|
p = (n.get("info") or {}).get("props") or {}
|
|
|
|
|
if p.get("media.class") == "Audio/Sink":
|
|
|
|
|
nm = p.get("node.name") or ""
|
|
|
|
|
if "iec958" in nm or "digital" in nm.lower():
|
|
|
|
|
print(nm)
|
|
|
|
|
break
|
|
|
|
|
')
|
|
|
|
|
[ -n "$TARGET" ] || { echo "找不到数字输出设备(iec958)"; exit 1; }
|
2026-09-19 17:22:03 +08:00
|
|
|
echo "数字输出: $TARGET"
|
|
|
|
|
|
2026-09-19 18:16:43 +08:00
|
|
|
# ---- 2. 部署 pipe 插件入口(必须 ASCII 路径) ----
|
|
|
|
|
mkdir -p "$(dirname "$WRAPPER")"
|
|
|
|
|
sed -e "s|__DSP__|$ROOT/dsp/loudness_norm.py|" "$ROOT/bin/collaplex-loudness-norm" > "$WRAPPER"
|
2026-09-19 18:34:56 +08:00
|
|
|
sed -e "s|__PY__|$ROOT/.venv/bin/python|" -e "s|__DSP__|$ROOT/dsp/eq.py|" \
|
|
|
|
|
"$ROOT/bin/collaplex-eq" > "$HOME/.local/bin/collaplex-eq"
|
|
|
|
|
chmod 755 "$WRAPPER" "$HOME/.local/bin/collaplex-eq"
|
|
|
|
|
echo "已装入口: $WRAPPER / $HOME/.local/bin/collaplex-eq"
|
2026-09-19 18:16:43 +08:00
|
|
|
|
|
|
|
|
# ---- 3. 渲染两份配置 ----
|
2026-09-19 18:09:29 +08:00
|
|
|
mkdir -p "$CONF_DIR"
|
2026-09-19 18:16:43 +08:00
|
|
|
sed -e "s|__WRAPPER__|$WRAPPER|" -e "s|__HRTF_IN__|$HRTF_IN|" \
|
2026-09-19 18:06:23 +08:00
|
|
|
"$ROOT/pipewire/10-collaplex-vsink.conf" > "$CONF_DIR/10-collaplex-vsink.conf"
|
2026-09-19 18:16:43 +08:00
|
|
|
sed -e "s|__HRIR__|$ROOT/hrir|" -e "s|__REVERB__|$ROOT/reverb/房间混响IR-96k.wav|" \
|
2026-09-19 18:34:56 +08:00
|
|
|
-e "s|__EQ_IN__|$EQ_IN|" -e "s|__WET__|$WET|" -e "s|__VOL__|$VOL|" \
|
2026-09-19 18:06:23 +08:00
|
|
|
"$ROOT/pipewire/20-collaplex-hrtf.conf" > "$CONF_DIR/20-collaplex-hrtf.conf"
|
2026-09-19 18:34:56 +08:00
|
|
|
sed -e "s|__EQ_WRAPPER__|$HOME/.local/bin/collaplex-eq|" -e "s|__TARGET__|$TARGET|" \
|
|
|
|
|
"$ROOT/pipewire/30-collaplex-eq.conf" > "$CONF_DIR/30-collaplex-eq.conf"
|
|
|
|
|
echo "已装配置: 10-collaplex-vsink.conf / 20-collaplex-hrtf.conf(湿量 $WET, 总音量 +${VOL_DB}dB) / 30-collaplex-eq.conf"
|
2026-09-19 17:22:03 +08:00
|
|
|
|
2026-09-19 18:16:43 +08:00
|
|
|
# ---- 4. 重启前清掉残留 DSP(占住共享槽位会让新实例起不来) ----
|
|
|
|
|
for p in /proc/[0-9]*; do
|
|
|
|
|
c=$(tr '\0' ' ' < "$p/cmdline" 2>/dev/null) || continue
|
|
|
|
|
case "$c" in
|
|
|
|
|
"/usr/bin/python3 "*dsp/loudness_norm.py*) kill -9 "${p#/proc/}" 2>/dev/null || true;;
|
2026-09-19 18:34:56 +08:00
|
|
|
*dsp/eq.py*) kill -9 "${p#/proc/}" 2>/dev/null || true;;
|
2026-09-19 18:16:43 +08:00
|
|
|
esac
|
|
|
|
|
done
|
|
|
|
|
sleep 1
|
|
|
|
|
|
|
|
|
|
# ---- 5. 重载 ----
|
2026-09-19 17:22:03 +08:00
|
|
|
systemctl --user restart pipewire pipewire-pulse wireplumber
|
2026-09-19 18:16:43 +08:00
|
|
|
sleep 8
|
2026-09-19 17:22:03 +08:00
|
|
|
|
2026-09-19 18:16:43 +08:00
|
|
|
# ---- 6. 自检 ----
|
|
|
|
|
echo "=== 自检 ==="
|
|
|
|
|
echo -n "节点: "; pw-dump | grep -o '"node.name": "collaplex[^"]*"' | sort -u | tr '\n' ' '; echo
|
|
|
|
|
n=0
|
|
|
|
|
for p in /proc/[0-9]*; do
|
|
|
|
|
c=$(tr '\0' ' ' < "$p/cmdline" 2>/dev/null) || continue
|
|
|
|
|
case "$c" in "/usr/bin/python3 "*dsp/loudness_norm.py*) n=$((n+1));; esac
|
2026-09-19 18:09:29 +08:00
|
|
|
done
|
2026-09-19 18:34:56 +08:00
|
|
|
echo "DSP 进程数: $n (归一化, 应为 2)"
|
|
|
|
|
m=0
|
|
|
|
|
for p in /proc/[0-9]*; do
|
|
|
|
|
c=$(tr '\0' ' ' < "$p/cmdline" 2>/dev/null) || continue
|
|
|
|
|
case "$c" in */dsp/eq.py*) m=$((m+1));; esac
|
|
|
|
|
done
|
|
|
|
|
echo "EQ 进程数: $m (应为 2)"
|
2026-09-19 18:16:43 +08:00
|
|
|
echo -n "默认输出: "; wpctl inspect @DEFAULT_SINK@ 2>/dev/null | grep -m1 "node.name" | sed 's/.*= //'
|