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", "clap",
"cpal", "cpal",
"log", "log",
"ringbuf",
"simple_logger", "simple_logger",
] ]
@ -285,6 +286,12 @@ dependencies = [
"windows", "windows",
] ]
[[package]]
name = "crossbeam-utils"
version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
[[package]] [[package]]
name = "dasp_sample" name = "dasp_sample"
version = "0.11.0" version = "0.11.0"
@ -645,6 +652,16 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 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]] [[package]]
name = "rustc-hash" name = "rustc-hash"
version = "1.1.0" version = "1.1.0"

View file

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

View file

@ -1,11 +1,17 @@
use std::error::Error; use std::error::Error;
use std::f64::consts::PI;
use std::process::exit; use std::process::exit;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::sync::mpsc::{channel, Receiver, Sender}; use std::sync::mpsc::{channel, Receiver, Sender};
use std::thread::sleep;
use std::time::{Duration, Instant};
use clap::{arg, command, Command}; 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 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() { fn main() {
simple_logger::init().unwrap(); simple_logger::init().unwrap();
@ -67,7 +73,39 @@ fn main() {
}; };
info!("Starting audio subsystem with input {} and output {}", input_device_name, output_device_name); 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", _)) => { Some(("list-inputs", _)) => {
let input_devices = match host.input_devices() { 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 { fn sampleformat_value(i: SampleFormat) -> usize {
match i { match i {
SampleFormat::U8 => 1, 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"); debug!("configuring output device");
let mut o_supported_configs_range = output.supported_output_configs()?; 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_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}"); let o_err_fn = |err| error!("error on the output stream: {err}");
// prepare the send channel // prepare the sample buffer
let (tx, rx) = channel::<i32>(); debug!("buffer capacity: {}", o_config.sample_rate.0 * 2);
let rx = Arc::new(Mutex::new(rx)); 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 { let o_stream = match o_sample_format {
SampleFormat::U8 => { 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) output.build_output_stream(&o_config, move |data: &mut [u8], c: &cpal::OutputCallbackInfo| { writer.write(data, c) }, o_err_fn, None)
}, },
SampleFormat::I8 => { 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) output.build_output_stream(&o_config, move |data: &mut [i8], c: &cpal::OutputCallbackInfo| { writer.write(data, c) }, o_err_fn, None)
}, },
SampleFormat::U16 => { 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) output.build_output_stream(&o_config, move |data: &mut [u16], c: &cpal::OutputCallbackInfo| { writer.write(data, c) }, o_err_fn, None)
}, },
SampleFormat::I16 => { 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) output.build_output_stream(&o_config, move |data: &mut [i16], c: &cpal::OutputCallbackInfo| { writer.write(data, c) }, o_err_fn, None)
}, },
SampleFormat::F32 => { 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) output.build_output_stream(&o_config, move |data: &mut [f32], c: &cpal::OutputCallbackInfo| { writer.write(data, c) }, o_err_fn, None)
}, },
SampleFormat::U32 => { 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) output.build_output_stream(&o_config, move |data: &mut [u32], c: &cpal::OutputCallbackInfo| { writer.write(data, c) }, o_err_fn, None)
}, },
SampleFormat::I32 => { 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) output.build_output_stream(&o_config, move |data: &mut [i32], c: &cpal::OutputCallbackInfo| { writer.write(data, c) }, o_err_fn, None)
}, },
SampleFormat::F64 => { 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) output.build_output_stream(&o_config, move |data: &mut [f64], c: &cpal::OutputCallbackInfo| { writer.write(data, c) }, o_err_fn, None)
}, },
SampleFormat::U64 => { 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) output.build_output_stream(&o_config, move |data: &mut [u64], c: &cpal::OutputCallbackInfo| { writer.write(data, c) }, o_err_fn, None)
}, },
SampleFormat::I64 => { 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) 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()) _ => 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 // u8 < i8 < u16 < i16 < u32 < i32 < f32 < f64 < u64 < i64
Ok(()) Ok(OutputChannelManager {
sampler: channelwriter,
stream: o_stream,
})
} }
struct AWriter { struct OutputChannelManager {
rx: Arc<Mutex<Receiver<i32>>> sampler: SampleChannel,
stream: Stream,
} }
trait AWritable { struct SampleChannel {
fn write<T: Sample + FromSample<i32>>(&self, data: &mut [T], _: &cpal::OutputCallbackInfo); inner: CachingProd<Arc<SharedRb<Heap<i32>>>>,
pub sample_rate: SampleRate,
i: usize
} }
impl SampleChannel {
impl AWritable for AWriter { fn write_frame(&mut self, frame: &[i32]) {
fn write<T: Sample + FromSample<i32>>(&self, data: &mut [T], _: &cpal::OutputCallbackInfo) { let len = frame.len();
for sample in data.iter_mut() { let mut iter = frame.into_iter();
match self.rx.lock().unwrap().try_recv() { let mut written = 0;
Ok(s) => { self.i += frame.len();
*sample = s.to_sample(); loop {
}, written += self.inner.push_iter((&mut iter).map(|u| *u));
Err(_) => { if written == len {
*sample = Sample::EQUILIBRIUM; 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);
}
}
} }