initial commit

This commit is contained in:
core 2024-09-30 10:33:40 -04:00
commit 9b6ccd3bd7
Signed by: core
GPG key ID: FDBF740DADDCEECF
8 changed files with 1414 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

5
.idea/.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/

11
.idea/ape-experiment.iml Normal file
View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="EMPTY_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
.idea/modules.xml Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/ape-experiment.iml" filepath="$PROJECT_DIR$/.idea/ape-experiment.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

1146
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

10
Cargo.toml Normal file
View file

@ -0,0 +1,10 @@
[package]
name = "ape-experiment"
version = "0.1.0"
edition = "2021"
[dependencies]
cpal = "0.15"
log = "0.4"
simple_logger = "5"
clap = { version = "4", features = ["cargo"] }

227
src/main.rs Normal file
View file

@ -0,0 +1,227 @@
use std::error::Error;
use std::process::exit;
use std::sync::{Arc, Mutex};
use std::sync::mpsc::{channel, Receiver, Sender};
use clap::{arg, command, Command};
use cpal::{Device, FromSample, Host, Sample, SampleFormat, SizedSample, SupportedStreamConfigRange};
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use log::{debug, error, info};
fn main() {
simple_logger::init().unwrap();
let matches = command!()
.subcommand(
Command::new("start")
.about("Starts the Audio Processing Experiment")
.arg(arg!(-i --input <INPUT> "Input device name"))
.arg(arg!(-o --output <OUTPUT> "Output device name"))
)
.subcommand(
Command::new("list-inputs")
)
.subcommand(
Command::new("list-outputs")
)
.subcommand_required(true)
.get_matches();
debug!("acquiring host");
let host = cpal::default_host();
match matches.subcommand() {
Some(("start", matches)) => {
let mut input_devices = match host.input_devices() {
Ok(i) => i,
Err(e) => {
error!("failed to enumerate available input devices: {e}");
exit(1);
}
};
let input_device_name = matches.get_one::<String>("input").unwrap().clone();
let input = match input_devices.find(|u| u.name().map_or(false, |u| u == input_device_name)) {
Some(i) => i,
None => {
error!("failed to find input by name {input_device_name} - try list-inputs");
exit(1);
}
};
let mut output_devices = match host.output_devices() {
Ok(i) => i,
Err(e) => {
error!("failed to enumerate available output devices: {e}");
exit(1);
}
};
let output_device_name = matches.get_one::<String>("output").unwrap().clone();
let output = match output_devices.find(|u| u.name().map_or(false, |u| u == output_device_name)) {
Some(i) => i,
None => {
error!("failed to find output by name {output_device_name} - try list-outputs");
exit(1);
}
};
info!("Starting audio subsystem with input {} and output {}", input_device_name, output_device_name);
start(host, input, output);
},
Some(("list-inputs", _)) => {
let input_devices = match host.input_devices() {
Ok(i) => i,
Err(e) => {
error!("failed to enumerate available input devices: {e}");
exit(1);
}
};
for device in input_devices {
println!("Available Input: {}", device.name().unwrap_or("[unavailable]".to_string()));
}
},
Some(("list-outputs", _)) => {
let output_devices = match host.output_devices() {
Ok(i) => i,
Err(e) => {
error!("failed to enumerate available output devices: {e}");
exit(1);
}
};
for device in output_devices {
println!("Available Output: {}", device.name().unwrap_or("[unavailable]".to_string()));
}
},
_ => {
error!("A valid subcommand is required, -h for help");
exit(1);
}
}
}
fn start(host: Host, input: Device, output: Device) {
match _start(host, input, output) {
Ok(_) => (),
Err(e) => {
error!("problem running: {e}");
exit(1);
}
}
}
fn sampleformat_value(i: SampleFormat) -> usize {
match i {
SampleFormat::U8 => 1,
SampleFormat::I8 => 2,
SampleFormat::U16 => 3,
SampleFormat::I16 => 4,
SampleFormat::F32 => 5,
SampleFormat::U32 => 6,
SampleFormat::I32 => 7,
SampleFormat::F64 => 8,
SampleFormat::U64 => 9,
SampleFormat::I64 => 10,
_ => 0
}
}
fn _start(host: Host, input: Device, output: Device) -> Result<(), Box<dyn Error>> {
debug!("configuring output device");
let mut o_supported_configs_range = output.supported_output_configs()?;
let mut o_best_range: SupportedStreamConfigRange = o_supported_configs_range.next().ok_or("No output configs available on this device (is it real?)")?;
let mut o_best_range_value = sampleformat_value(o_best_range.sample_format());
for range in o_supported_configs_range {
println!("{:?}", range);
if sampleformat_value(range.sample_format()) > o_best_range_value {
o_best_range = range;
o_best_range_value = sampleformat_value(o_best_range.sample_format());
}
}
let o_sample_format = o_best_range.sample_format();
let o_config = o_best_range.with_max_sample_rate().into();
let o_err_fn = |err| error!("error on the output stream: {err}");
// prepare the send channel
let (tx, rx) = channel::<i32>();
let rx = Arc::new(Mutex::new(rx));
let o_stream = match o_sample_format {
SampleFormat::U8 => {
let writer: AWriter = AWriter { rx };
output.build_output_stream(&o_config, move |data: &mut [u8], c: &cpal::OutputCallbackInfo| { writer.write(data, c) }, o_err_fn, None)
},
SampleFormat::I8 => {
let writer: AWriter = AWriter { rx };
output.build_output_stream(&o_config, move |data: &mut [i8], c: &cpal::OutputCallbackInfo| { writer.write(data, c) }, o_err_fn, None)
},
SampleFormat::U16 => {
let writer: AWriter = AWriter { rx };
output.build_output_stream(&o_config, move |data: &mut [u16], c: &cpal::OutputCallbackInfo| { writer.write(data, c) }, o_err_fn, None)
},
SampleFormat::I16 => {
let writer: AWriter = AWriter { rx };
output.build_output_stream(&o_config, move |data: &mut [i16], c: &cpal::OutputCallbackInfo| { writer.write(data, c) }, o_err_fn, None)
},
SampleFormat::F32 => {
let writer: AWriter = AWriter { rx };
output.build_output_stream(&o_config, move |data: &mut [f32], c: &cpal::OutputCallbackInfo| { writer.write(data, c) }, o_err_fn, None)
},
SampleFormat::U32 => {
let writer: AWriter = AWriter { rx };
output.build_output_stream(&o_config, move |data: &mut [u32], c: &cpal::OutputCallbackInfo| { writer.write(data, c) }, o_err_fn, None)
},
SampleFormat::I32 => {
let writer: AWriter = AWriter { rx };
output.build_output_stream(&o_config, move |data: &mut [i32], c: &cpal::OutputCallbackInfo| { writer.write(data, c) }, o_err_fn, None)
},
SampleFormat::F64 => {
let writer: AWriter = AWriter { rx };
output.build_output_stream(&o_config, move |data: &mut [f64], c: &cpal::OutputCallbackInfo| { writer.write(data, c) }, o_err_fn, None)
},
SampleFormat::U64 => {
let writer: AWriter = AWriter { rx };
output.build_output_stream(&o_config, move |data: &mut [u64], c: &cpal::OutputCallbackInfo| { writer.write(data, c) }, o_err_fn, None)
},
SampleFormat::I64 => {
let writer: AWriter = AWriter { rx };
output.build_output_stream(&o_config, move |data: &mut [i64], c: &cpal::OutputCallbackInfo| { writer.write(data, c) }, o_err_fn, None)
},
_ => return Err(format!("Unsupported sampling format {o_sample_format}").into())
}?;
o_stream.play()?;
loop {}
// u8 < i8 < u16 < i16 < u32 < i32 < f32 < f64 < u64 < i64
Ok(())
}
struct AWriter {
rx: Arc<Mutex<Receiver<i32>>>
}
trait AWritable {
fn write<T: Sample + FromSample<i32>>(&self, data: &mut [T], _: &cpal::OutputCallbackInfo);
}
impl AWritable for AWriter {
fn write<T: Sample + FromSample<i32>>(&self, data: &mut [T], _: &cpal::OutputCallbackInfo) {
for sample in data.iter_mut() {
match self.rx.lock().unwrap().try_recv() {
Ok(s) => {
*sample = s.to_sample();
},
Err(_) => {
*sample = Sample::EQUILIBRIUM;
}
}
}
}
}