tone generation

This commit is contained in:
core 2024-10-01 19:32:28 -04:00
parent 9b6ccd3bd7
commit 358cd0b052
Signed by: core
GPG key ID: 9D0DAED5555DD0B4
3 changed files with 140 additions and 47 deletions

17
Cargo.lock generated
View file

@ -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"

View file

@ -8,3 +8,4 @@ cpal = "0.15"
log = "0.4"
simple_logger = "5"
clap = { version = "4", features = ["cargo"] }
ringbuf = "0.4"

View file

@ -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 OutputChannelManager {
sampler: SampleChannel,
stream: Stream,
}
struct SampleChannel {
inner: CachingProd<Arc<SharedRb<Heap<i32>>>>,
pub sample_rate: SampleRate,
i: usize
}
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: Arc<Mutex<Receiver<i32>>>
rx: CachingCons<Arc<SharedRb<Heap<i32>>>>,
default_sample: i32
}
trait AWritable {
fn write<T: Sample + FromSample<i32>>(&self, data: &mut [T], _: &cpal::OutputCallbackInfo);
fn write<T: Sample + FromSample<i32>>(&mut self, data: &mut [T], _: &cpal::OutputCallbackInfo);
}
impl AWritable for AWriter {
fn write<T: Sample + FromSample<i32>>(&self, data: &mut [T], _: &cpal::OutputCallbackInfo) {
fn write<T: Sample + FromSample<i32>>(&mut self, data: &mut [T], _: &cpal::OutputCallbackInfo) {
let mut i = 0;
for sample in data.iter_mut() {
match self.rx.lock().unwrap().try_recv() {
Ok(s) => {
*sample = s.to_sample();
*sample = match self.rx.try_pop() {
None => {
i += 1;
self.default_sample.to_sample()
},
Err(_) => {
*sample = Sample::EQUILIBRIUM;
}
}
Some(v) => v.to_sample()
};
}
if i > 0 {
trace!("<AWriter as AWritable>::write<T> underflow data req n={} e={}", data.len(), i);
}
}
}