Add support for --loopback-no-mix flag and topology synchronization

- Introduced the `--loopback-no-mix` flag to control 2-node vs 4-node loopback topologies.
- Updated topology handling logic to recompute and migrate as needed based on loopback flags.
- Enhanced database schema to include `loopback_no_mix` with automatic migration for backward compatibility.
- Refactored `create`, `edit`, and `route` commands to integrate new topology management capabilities.
- Improved `list` command output to include architecture details.
This commit is contained in:
2026-08-11 15:33:36 +02:00
parent be70c55728
commit 493f91bc28
10 changed files with 308 additions and 42 deletions

View File

@@ -29,6 +29,11 @@ pub struct VmicState {
// pitfalls here, unlike PipeWire's JSON dump (see pw::text).
pub mic_source: Option<String>,
pub mic_volume_pct: Option<u8>,
// Opt-in: keep the 2-node topology even when a source is mixed in while
// the self-monitor loopback is on, accepting that the mix becomes
// audible in the loopback instead of paying for a 4-node split.
pub loopback_no_mix: bool,
}
fn db_path() -> PathBuf {
@@ -90,12 +95,33 @@ pub fn open_db() -> Result<Connection> {
loopback_id INTEGER,
volume_pct INTEGER,
mic_source TEXT,
mic_volume_pct INTEGER
mic_volume_pct INTEGER,
loopback_no_mix INTEGER NOT NULL DEFAULT 0
);",
)?;
migrate_add_loopback_no_mix(&conn)?;
Ok(conn)
}
/// `CREATE TABLE IF NOT EXISTS` above only covers fresh databases; an
/// existing `vmic.db` from before `loopback_no_mix` existed needs the column
/// added explicitly. Safe to call unconditionally - a no-op once the column
/// is present either way.
fn migrate_add_loopback_no_mix(conn: &Connection) -> Result<()> {
let has_column = conn
.prepare("PRAGMA table_info(vmics)")?
.query_map([], |row| row.get::<_, String>(1))?
.filter_map(std::result::Result::ok)
.any(|c| c == "loopback_no_mix");
if !has_column {
conn.execute(
"ALTER TABLE vmics ADD COLUMN loopback_no_mix INTEGER NOT NULL DEFAULT 0",
[],
)?;
}
Ok(())
}
impl VmicState {
fn from_row(row: &rusqlite::Row) -> rusqlite::Result<Self> {
Ok(Self {
@@ -112,6 +138,7 @@ impl VmicState {
volume_pct: row.get("volume_pct")?,
mic_source: row.get("mic_source")?,
mic_volume_pct: row.get("mic_volume_pct")?,
loopback_no_mix: row.get("loopback_no_mix")?,
})
}
@@ -135,8 +162,8 @@ impl VmicState {
"INSERT INTO vmics (
name, sink_name, mid_name, mix_name, source_name,
sink_node_id, mix_node_id, sink_pid, mix_pid, loopback_id, volume_pct,
mic_source, mic_volume_pct
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13)
mic_source, mic_volume_pct, loopback_no_mix
) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14)
ON CONFLICT(name) DO UPDATE SET
sink_name = excluded.sink_name,
mid_name = excluded.mid_name,
@@ -149,7 +176,8 @@ impl VmicState {
loopback_id = excluded.loopback_id,
volume_pct = excluded.volume_pct,
mic_source = excluded.mic_source,
mic_volume_pct = excluded.mic_volume_pct",
mic_volume_pct = excluded.mic_volume_pct,
loopback_no_mix = excluded.loopback_no_mix",
params![
self.name,
self.sink_name,
@@ -164,6 +192,7 @@ impl VmicState {
self.volume_pct,
self.mic_source,
self.mic_volume_pct,
self.loopback_no_mix,
],
)?;
Ok(())