Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
206 changes: 180 additions & 26 deletions tuitar/src/input.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use cpal::{Stream, StreamConfig};
use cpal::{FromSample, Sample, SampleFormat, SizedSample, Stream, StreamConfig};

pub struct Recorder {
stream: Stream,
Expand All @@ -13,45 +13,199 @@ impl Recorder {
{
let host = cpal::default_host();

let device = host
.input_devices()
.unwrap()
.find(|d| d.name().unwrap().contains(device_name))
.expect("No pipewire input device found");
let requested = device_name.to_lowercase();
let mut matching_device = None;

println!("Using input device: {:?}", device.name().unwrap());
match host.input_devices() {
Ok(devices) => {
for device in devices {
match device.name() {
Ok(name) => {
if name.to_lowercase().contains(&requested) {
matching_device = Some((device, name));
break;
}
}
Err(err) => {
eprintln!("Skipping input device with unreadable name: {err}");
}
}
}
}
Err(err) => {
eprintln!("Failed to enumerate input devices: {err}");
}
}

let (device, device_name) = if let Some(device) = matching_device {
device
} else {
let fallback = host
.default_input_device()
.expect("No audio input devices detected");

let name = fallback
.name()
.unwrap_or_else(|err| format!("unknown device ({err})"));
println!("Falling back to default input device: {name}");
(fallback, name)
};

println!("Using input device: {device_name}");

let supported_config = match device.default_input_config() {
Ok(config) => config,
Err(err) => {
eprintln!("Failed to fetch default input config: {err}");

let mut chosen_config = None;
match device.supported_input_configs() {
Ok(mut configs) => {
while let Some(range) = configs.next() {
let config = range.with_max_sample_rate();

let mut supported_configs_range = device.supported_input_configs().unwrap();
let supported_config = supported_configs_range
.next()
.expect("no supported config")
.with_max_sample_rate()
.config();
if config.sample_format() == SampleFormat::I16 {
chosen_config = Some(config);
break;
}

println!("Using config: {supported_config:?}");
if chosen_config.is_none() {
chosen_config = Some(config);
}
}
}
Err(list_err) => {
eprintln!("Unable to read supported input configs: {list_err}");
}
}

chosen_config.expect("Device has no supported audio configs")
}
};

let sample_format = supported_config.sample_format();
let stream_config = supported_config.config();

println!("Using config: {stream_config:?}");
println!("Using sample format: {sample_format:?}");

let mut device = Some(device);
let mut stream_config = Some(stream_config);
let mut callback = Some(callback);

match sample_format {
SampleFormat::I16 => Self::build_passthrough(
device.take().unwrap(),
stream_config.take().unwrap(),
callback.take().unwrap(),
),
SampleFormat::U16 => Self::build_stream_with_conversion::<u16, _>(
device.take().unwrap(),
stream_config.take().unwrap(),
callback.take().unwrap(),
),
SampleFormat::I32 => Self::build_stream_with_conversion::<i32, _>(
device.take().unwrap(),
stream_config.take().unwrap(),
callback.take().unwrap(),
),
SampleFormat::U32 => Self::build_stream_with_conversion::<u32, _>(
device.take().unwrap(),
stream_config.take().unwrap(),
callback.take().unwrap(),
),
SampleFormat::F32 => Self::build_stream_with_conversion::<f32, _>(
device.take().unwrap(),
stream_config.take().unwrap(),
callback.take().unwrap(),
),
SampleFormat::F64 => Self::build_stream_with_conversion::<f64, _>(
device.take().unwrap(),
stream_config.take().unwrap(),
callback.take().unwrap(),
),
SampleFormat::I8 => Self::build_stream_with_conversion::<i8, _>(
device.take().unwrap(),
stream_config.take().unwrap(),
callback.take().unwrap(),
),
SampleFormat::U8 => Self::build_stream_with_conversion::<u8, _>(
device.take().unwrap(),
stream_config.take().unwrap(),
callback.take().unwrap(),
),
SampleFormat::I64 => Self::build_stream_with_conversion::<i64, _>(
device.take().unwrap(),
stream_config.take().unwrap(),
callback.take().unwrap(),
),
SampleFormat::U64 => Self::build_stream_with_conversion::<u64, _>(
device.take().unwrap(),
stream_config.take().unwrap(),
callback.take().unwrap(),
),
_ => panic!("Unsupported sample format: {sample_format:?}"),
}
}

pub fn start(&self) {
self.stream.play().unwrap();
}

pub fn sample_rate(&self) -> u32 {
self.config.sample_rate.0
}
}

impl Recorder {
fn build_passthrough<D>(device: cpal::Device, config: StreamConfig, mut callback: D) -> Self
where
D: FnMut(&[i16], &cpal::InputCallbackInfo) + Send + 'static,
{
let stream = device
.build_input_stream(
&supported_config,
callback,
&config,
move |data: &[i16], info| {
callback(data, info);
},
|err| {
panic!("Error: {err}");
},
None,
)
.unwrap();
.unwrap_or_else(|err| panic!("Failed to build input stream: {err}"));

Self {
stream,
config: supported_config,
}
Self { stream, config }
}

pub fn start(&self) {
self.stream.play().unwrap();
}
fn build_stream_with_conversion<T, D>(
device: cpal::Device,
config: StreamConfig,
mut callback: D,
) -> Self
where
T: Sample + SizedSample,
i16: FromSample<T>,
D: FnMut(&[i16], &cpal::InputCallbackInfo) + Send + 'static,
{
let mut scratch: Vec<i16> = Vec::new();

pub fn sample_rate(&self) -> u32 {
self.config.sample_rate.0
let stream = device
.build_input_stream(
&config,
move |data: &[T], info| {
scratch.clear();
scratch.reserve(data.len());
scratch.extend(data.iter().map(|sample| (*sample).to_sample::<i16>()));
callback(&scratch, info);
},
|err| {
panic!("Error: {err}");
},
None,
)
.unwrap_or_else(|err| panic!("Failed to build input stream: {err}"));

Self { stream, config }
}
}
Loading