From 358cd0b0526f647e1b7a8ad1645289938f88a0bb Mon Sep 17 00:00:00 2001 From: core Date: Tue, 1 Oct 2024 19:32:28 -0400 Subject: [PATCH] tone generation --- Cargo.lock | 17 ++++++ Cargo.toml | 3 +- src/main.rs | 167 +++++++++++++++++++++++++++++++++++++--------------- 3 files changed, 140 insertions(+), 47 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0e94c4d..4d73183 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 9260c00..91ef56c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,4 +7,5 @@ edition = "2021" cpal = "0.15" log = "0.4" simple_logger = "5" -clap = { version = "4", features = ["cargo"] } \ No newline at end of file +clap = { version = "4", features = ["cargo"] } +ringbuf = "0.4" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 1766575..7b217de 100644 --- a/src/main.rs +++ b/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 = vec![0.0f32.to_sample(); (output_writer.sampler.sample_rate.0 / 10) as usize]; + output_writer.sampler.write_frame(&frames); + + let mut samples: Vec = 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> { +fn start(host: Host, input: Device, output: Device) -> Result> { 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(); - let rx = Arc::new(Mutex::new(rx)); + // prepare the sample buffer + debug!("buffer capacity: {}", o_config.sample_rate.0 * 2); + let buf = HeapRb::::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>> +struct OutputChannelManager { + sampler: SampleChannel, + stream: Stream, } -trait AWritable { - fn write>(&self, data: &mut [T], _: &cpal::OutputCallbackInfo); +struct SampleChannel { + inner: CachingProd>>>, + pub sample_rate: SampleRate, + i: usize } - -impl AWritable for AWriter { - fn write>(&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>>>, + default_sample: i32 +} + +trait AWritable { + fn write>(&mut self, data: &mut [T], _: &cpal::OutputCallbackInfo); +} + +impl AWritable for AWriter { + fn write>(&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!("::write underflow data req n={} e={}", data.len(), i); + } + } }