tone generation
This commit is contained in:
parent
9b6ccd3bd7
commit
358cd0b052
3 changed files with 140 additions and 47 deletions
17
Cargo.lock
generated
17
Cargo.lock
generated
|
@ -89,6 +89,7 @@ dependencies = [
|
|||
"clap",
|
||||
"cpal",
|
||||
"log",
|
||||
"ringbuf",
|
||||
"simple_logger",
|
||||
]
|
||||
|
||||
|
@ -285,6 +286,12 @@ dependencies = [
|
|||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
|
||||
|
||||
[[package]]
|
||||
name = "dasp_sample"
|
||||
version = "0.11.0"
|
||||
|
@ -645,6 +652,16 @@ version = "0.8.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "ringbuf"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "726bb493fe9cac765e8f96a144c3a8396bdf766dedad22e504b70b908dcbceb4"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
|
|
|
@ -8,3 +8,4 @@ cpal = "0.15"
|
|||
log = "0.4"
|
||||
simple_logger = "5"
|
||||
clap = { version = "4", features = ["cargo"] }
|
||||
ringbuf = "0.4"
|
167
src/main.rs
167
src/main.rs
|
@ -1,11 +1,17 @@
|
|||
use std::error::Error;
|
||||
use std::f64::consts::PI;
|
||||
use std::process::exit;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::mpsc::{channel, Receiver, Sender};
|
||||
use std::thread::sleep;
|
||||
use std::time::{Duration, Instant};
|
||||
use clap::{arg, command, Command};
|
||||
use cpal::{Device, FromSample, Host, Sample, SampleFormat, SizedSample, SupportedStreamConfigRange};
|
||||
use cpal::{Device, FromSample, Host, Sample, SampleFormat, SampleRate, SizedSample, Stream, StreamConfig, SupportedStreamConfigRange};
|
||||
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||
use log::{debug, error, info};
|
||||
use log::{debug, error, info, trace};
|
||||
use ringbuf::{CachingCons, CachingProd, HeapRb, SharedRb};
|
||||
use ringbuf::storage::Heap;
|
||||
use ringbuf::traits::{Consumer, Producer, Split};
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
|
@ -67,7 +73,39 @@ fn main() {
|
|||
};
|
||||
|
||||
info!("Starting audio subsystem with input {} and output {}", input_device_name, output_device_name);
|
||||
start(host, input, output);
|
||||
let mut output_writer = match start(host, input, output) {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
error!("problem running: {e}");
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let sample_pause = 1.0 / output_writer.sampler.sample_rate.0 as f64;
|
||||
let mut t: u64 = 0;
|
||||
|
||||
let frequency = 600.0; // hz
|
||||
let amp = 0.5;
|
||||
|
||||
// push a bunch of silence to allow us to catch up
|
||||
let frames: Vec<i32> = vec![0.0f32.to_sample(); (output_writer.sampler.sample_rate.0 / 10) as usize];
|
||||
output_writer.sampler.write_frame(&frames);
|
||||
|
||||
let mut samples: Vec<i32> = vec![];
|
||||
|
||||
let dur = Duration::from_secs_f64(1.0 / (output_writer.sampler.sample_rate.0 as f64 * 4.0));
|
||||
|
||||
loop {
|
||||
let start = Instant::now();
|
||||
t += 1;
|
||||
|
||||
let t_s = t as f64 / output_writer.sampler.sample_rate.0 as f64;
|
||||
let sample = (2.0 * PI * frequency * t_s).sin() * amp;
|
||||
|
||||
output_writer.sampler.write_one(sample.to_sample());
|
||||
|
||||
//sleep(dur);
|
||||
}
|
||||
},
|
||||
Some(("list-inputs", _)) => {
|
||||
let input_devices = match host.input_devices() {
|
||||
|
@ -100,16 +138,6 @@ fn main() {
|
|||
}
|
||||
}
|
||||
|
||||
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,
|
||||
|
@ -126,7 +154,7 @@ fn sampleformat_value(i: SampleFormat) -> usize {
|
|||
}
|
||||
}
|
||||
|
||||
fn _start(host: Host, input: Device, output: Device) -> Result<(), Box<dyn Error>> {
|
||||
fn start(host: Host, input: Device, output: Device) -> Result<OutputChannelManager, Box<dyn Error>> {
|
||||
debug!("configuring output device");
|
||||
let mut o_supported_configs_range = output.supported_output_configs()?;
|
||||
|
||||
|
@ -142,86 +170,133 @@ fn _start(host: Host, input: Device, output: Device) -> Result<(), Box<dyn Error
|
|||
}
|
||||
|
||||
let o_sample_format = o_best_range.sample_format();
|
||||
let o_config = o_best_range.with_max_sample_rate().into();
|
||||
let o_config: StreamConfig = 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));
|
||||
// prepare the sample buffer
|
||||
debug!("buffer capacity: {}", o_config.sample_rate.0 * 2);
|
||||
let buf = HeapRb::<i32>::new((o_config.sample_rate.0 * 2) as usize);
|
||||
let (prod, cons) = buf.split();
|
||||
let channelwriter = SampleChannel { inner: prod, sample_rate: o_config.sample_rate, i: 0 };
|
||||
let default_sample = Sample::EQUILIBRIUM;
|
||||
|
||||
let o_stream = match o_sample_format {
|
||||
SampleFormat::U8 => {
|
||||
let writer: AWriter = AWriter { rx };
|
||||
let mut writer: AWriter = AWriter { rx: cons,default_sample };
|
||||
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 };
|
||||
let mut writer: AWriter = AWriter { rx: cons,default_sample };
|
||||
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 };
|
||||
let mut writer: AWriter = AWriter { rx: cons,default_sample };
|
||||
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 };
|
||||
let mut writer: AWriter = AWriter { rx: cons,default_sample };
|
||||
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 };
|
||||
let mut writer: AWriter = AWriter { rx: cons,default_sample };
|
||||
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 };
|
||||
let mut writer: AWriter = AWriter { rx: cons,default_sample };
|
||||
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 };
|
||||
let mut writer: AWriter = AWriter { rx: cons,default_sample };
|
||||
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 };
|
||||
let mut writer: AWriter = AWriter { rx: cons,default_sample };
|
||||
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 };
|
||||
let mut writer: AWriter = AWriter { rx: cons,default_sample };
|
||||
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 };
|
||||
let mut writer: AWriter = AWriter { rx: cons,default_sample };
|
||||
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()?;
|
||||
debug!("Starting output stream with config {:?}", o_config);
|
||||
|
||||
loop {}
|
||||
o_stream.play()?;
|
||||
|
||||
// u8 < i8 < u16 < i16 < u32 < i32 < f32 < f64 < u64 < i64
|
||||
|
||||
Ok(())
|
||||
Ok(OutputChannelManager {
|
||||
sampler: channelwriter,
|
||||
stream: o_stream,
|
||||
})
|
||||
}
|
||||
|
||||
struct AWriter {
|
||||
rx: Arc<Mutex<Receiver<i32>>>
|
||||
struct OutputChannelManager {
|
||||
sampler: SampleChannel,
|
||||
stream: Stream,
|
||||
}
|
||||
|
||||
trait AWritable {
|
||||
fn write<T: Sample + FromSample<i32>>(&self, data: &mut [T], _: &cpal::OutputCallbackInfo);
|
||||
struct SampleChannel {
|
||||
inner: CachingProd<Arc<SharedRb<Heap<i32>>>>,
|
||||
pub sample_rate: SampleRate,
|
||||
i: usize
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
impl SampleChannel {
|
||||
fn write_frame(&mut self, frame: &[i32]) {
|
||||
let len = frame.len();
|
||||
let mut iter = frame.into_iter();
|
||||
let mut written = 0;
|
||||
self.i += frame.len();
|
||||
loop {
|
||||
written += self.inner.push_iter((&mut iter).map(|u| *u));
|
||||
if written == len {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
fn write_one(&mut self, sample: i32) {
|
||||
self.i += 1;
|
||||
loop {
|
||||
match self.inner.try_push(sample) {
|
||||
Ok(_) => return,
|
||||
Err(_) => continue
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
struct AWriter {
|
||||
rx: CachingCons<Arc<SharedRb<Heap<i32>>>>,
|
||||
default_sample: i32
|
||||
}
|
||||
|
||||
trait AWritable {
|
||||
fn write<T: Sample + FromSample<i32>>(&mut self, data: &mut [T], _: &cpal::OutputCallbackInfo);
|
||||
}
|
||||
|
||||
impl AWritable for AWriter {
|
||||
fn write<T: Sample + FromSample<i32>>(&mut self, data: &mut [T], _: &cpal::OutputCallbackInfo) {
|
||||
let mut i = 0;
|
||||
|
||||
for sample in data.iter_mut() {
|
||||
*sample = match self.rx.try_pop() {
|
||||
None => {
|
||||
i += 1;
|
||||
self.default_sample.to_sample()
|
||||
},
|
||||
Some(v) => v.to_sample()
|
||||
};
|
||||
}
|
||||
|
||||
if i > 0 {
|
||||
trace!("<AWriter as AWritable>::write<T> underflow data req n={} e={}", data.len(), i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue