initial commit
This commit is contained in:
commit
9b6ccd3bd7
8 changed files with 1414 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
5
.idea/.gitignore
vendored
Normal file
5
.idea/.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
11
.idea/ape-experiment.iml
Normal file
11
.idea/ape-experiment.iml
Normal 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
8
.idea/modules.xml
Normal 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
6
.idea/vcs.xml
Normal 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
1146
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
10
Cargo.toml
Normal file
10
Cargo.toml
Normal 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
227
src/main.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue