Run as 'vois': install via make, config file, Auto-Tune, more presets, custom chains

- 'vois' installs to PATH (make install) and works with no flags:
  auto device selection (pipewire/pulse/default) and 48 kHz default
- ~/.config/vois/config.toml for defaults + custom preset chains (serde)
- Auto-Tune pitch correction (snap to nearest semitone, smoothed)
- 5 new presets: Chipmunk, Helium, Vader, Telephone, Growl (17 total)
- --pitch-correct flag, --no-virtual-mic to disable the default virtual mic
- no dead-code allowances; clippy/fmt/tests clean
This commit is contained in:
loki5512344 2026-08-02 18:20:36 +02:00
parent b0d685a110
commit aacaf7d9cf
14 changed files with 753 additions and 285 deletions

View file

@ -6,6 +6,8 @@ use cpal::{
};
use ringbuf::traits::Producer;
const PREFERRED_INPUTS: [&str; 3] = ["pipewire", "pulse", "default"];
pub fn find_input(host: &Host, name: Option<&str>) -> Result<Device> {
match name {
Some(name) => host
@ -13,9 +15,20 @@ pub fn find_input(host: &Host, name: Option<&str>) -> Result<Device> {
.context("failed to enumerate input devices")?
.find(|d| d.name().map(|n| n == name).unwrap_or(false))
.with_context(|| format!("input device {name:?} not found")),
None => host
.default_input_device()
.context("no default input device found"),
None => {
let mut devices = host
.input_devices()
.context("failed to enumerate input devices")?;
if let Some(device) = devices.find(|d| {
d.name()
.map(|n| PREFERRED_INPUTS.iter().any(|p| n == *p))
.unwrap_or(false)
}) {
return Ok(device);
}
host.default_input_device()
.context("no default input device found")
}
}
}

View file

@ -53,29 +53,42 @@ pub fn run(cfg: &AppConfig) -> Result<()> {
let in_supported = input_device
.default_input_config()
.context("failed to read default input config")?;
let rate = cfg
.sample_rate
.map(SampleRate)
.unwrap_or_else(|| in_supported.sample_rate());
let in_supported = if cfg.sample_rate.is_some() {
pick_config(
input_device.supported_input_configs()?,
in_supported.channels(),
rate,
)
.with_context(|| format!("input device does not support sample rate {} Hz", rate.0))?
} else {
in_supported
};
let in_channels = in_supported.channels();
let out_default = output_device
.default_output_config()
.context("failed to read default output config")?;
let out_channels = out_default.channels();
// Negotiate one sample rate both devices support, preferring 48 kHz.
let rate = if let Some(r) = cfg.sample_rate {
SampleRate(r)
} else {
let in_ranges = input_device.supported_input_configs()?;
let out_ranges = output_device.supported_output_configs()?;
let in_ranges: Vec<_> = in_ranges.collect();
let out_ranges: Vec<_> = out_ranges.collect();
let mut chosen = in_supported.sample_rate();
for r in [
SampleRate(48000),
SampleRate(44100),
in_supported.sample_rate(),
] {
if pick_config(in_ranges.iter().cloned(), in_channels, r).is_some()
&& pick_config(out_ranges.iter().cloned(), out_channels, r).is_some()
{
chosen = r;
break;
}
}
chosen
};
let in_supported = pick_config(input_device.supported_input_configs()?, in_channels, rate)
.with_context(|| format!("input device does not support sample rate {} Hz", rate.0))?;
let out_supported = pick_config(
output_device.supported_output_configs()?,
out_default.channels(),
out_channels,
rate,
)
.with_context(|| {

View file

@ -6,6 +6,8 @@ use cpal::{
};
use ringbuf::traits::Consumer;
const PREFERRED_OUTPUTS: [&str; 3] = ["pipewire", "pulse", "default"];
pub fn find_output(host: &Host, name: Option<&str>) -> Result<Device> {
match name {
Some(name) => host
@ -13,9 +15,20 @@ pub fn find_output(host: &Host, name: Option<&str>) -> Result<Device> {
.context("failed to enumerate output devices")?
.find(|d| d.name().map(|n| n == name).unwrap_or(false))
.with_context(|| format!("output device {name:?} not found")),
None => host
.default_output_device()
.context("no default output device found"),
None => {
let mut devices = host
.output_devices()
.context("failed to enumerate output devices")?;
if let Some(device) = devices.find(|d| {
d.name()
.map(|n| PREFERRED_OUTPUTS.iter().any(|p| n == *p))
.unwrap_or(false)
}) {
return Ok(device);
}
host.default_output_device()
.context("no default output device found")
}
}
}