mirror of
https://github.com/project-slippi/cpal.git
synced 2025-10-06 00:02:40 +02:00
Each *_wave() function calls advance_sample(). Having tick() also call it results in skipping every second sample.
192 lines
5.9 KiB
Rust
192 lines
5.9 KiB
Rust
/* This example expose parameter to pass generator of sample.
|
|
Good starting point for integration of cpal into your application.
|
|
*/
|
|
|
|
extern crate anyhow;
|
|
extern crate clap;
|
|
extern crate cpal;
|
|
|
|
use cpal::{
|
|
traits::{DeviceTrait, HostTrait, StreamTrait},
|
|
SizedSample,
|
|
};
|
|
use cpal::{FromSample, Sample};
|
|
|
|
fn main() -> anyhow::Result<()> {
|
|
let stream = stream_setup_for()?;
|
|
stream.play()?;
|
|
std::thread::sleep(std::time::Duration::from_millis(4000));
|
|
Ok(())
|
|
}
|
|
|
|
pub enum Waveform {
|
|
Sine,
|
|
Square,
|
|
Saw,
|
|
Triangle,
|
|
}
|
|
|
|
pub struct Oscillator {
|
|
pub sample_rate: f32,
|
|
pub waveform: Waveform,
|
|
pub current_sample_index: f32,
|
|
pub frequency_hz: f32,
|
|
}
|
|
|
|
impl Oscillator {
|
|
fn advance_sample(&mut self) {
|
|
self.current_sample_index = (self.current_sample_index + 1.0) % self.sample_rate;
|
|
}
|
|
|
|
fn set_waveform(&mut self, waveform: Waveform) {
|
|
self.waveform = waveform;
|
|
}
|
|
|
|
fn calculate_sine_output_from_freq(&self, freq: f32) -> f32 {
|
|
let two_pi = 2.0 * std::f32::consts::PI;
|
|
(self.current_sample_index * freq * two_pi / self.sample_rate).sin()
|
|
}
|
|
|
|
fn is_multiple_of_freq_above_nyquist(&self, multiple: f32) -> bool {
|
|
self.frequency_hz * multiple > self.sample_rate / 2.0
|
|
}
|
|
|
|
fn sine_wave(&mut self) -> f32 {
|
|
self.advance_sample();
|
|
self.calculate_sine_output_from_freq(self.frequency_hz)
|
|
}
|
|
|
|
fn generative_waveform(&mut self, harmonic_index_increment: i32, gain_exponent: f32) -> f32 {
|
|
self.advance_sample();
|
|
let mut output = 0.0;
|
|
let mut i = 1;
|
|
while !self.is_multiple_of_freq_above_nyquist(i as f32) {
|
|
let gain = 1.0 / (i as f32).powf(gain_exponent);
|
|
output += gain * self.calculate_sine_output_from_freq(self.frequency_hz * i as f32);
|
|
i += harmonic_index_increment;
|
|
}
|
|
output
|
|
}
|
|
|
|
fn square_wave(&mut self) -> f32 {
|
|
self.generative_waveform(2, 1.0)
|
|
}
|
|
|
|
fn saw_wave(&mut self) -> f32 {
|
|
self.generative_waveform(1, 1.0)
|
|
}
|
|
|
|
fn triangle_wave(&mut self) -> f32 {
|
|
self.generative_waveform(2, 2.0)
|
|
}
|
|
|
|
fn tick(&mut self) -> f32 {
|
|
match self.waveform {
|
|
Waveform::Sine => self.sine_wave(),
|
|
Waveform::Square => self.square_wave(),
|
|
Waveform::Saw => self.saw_wave(),
|
|
Waveform::Triangle => self.triangle_wave(),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn stream_setup_for() -> Result<cpal::Stream, anyhow::Error>
|
|
where
|
|
{
|
|
let (_host, device, config) = host_device_setup()?;
|
|
|
|
match config.sample_format() {
|
|
cpal::SampleFormat::I8 => make_stream::<i8>(&device, &config.into()),
|
|
cpal::SampleFormat::I16 => make_stream::<i16>(&device, &config.into()),
|
|
cpal::SampleFormat::I32 => make_stream::<i32>(&device, &config.into()),
|
|
cpal::SampleFormat::I64 => make_stream::<i64>(&device, &config.into()),
|
|
cpal::SampleFormat::U8 => make_stream::<u8>(&device, &config.into()),
|
|
cpal::SampleFormat::U16 => make_stream::<u16>(&device, &config.into()),
|
|
cpal::SampleFormat::U32 => make_stream::<u32>(&device, &config.into()),
|
|
cpal::SampleFormat::U64 => make_stream::<u64>(&device, &config.into()),
|
|
cpal::SampleFormat::F32 => make_stream::<f32>(&device, &config.into()),
|
|
cpal::SampleFormat::F64 => make_stream::<f64>(&device, &config.into()),
|
|
sample_format => Err(anyhow::Error::msg(format!(
|
|
"Unsupported sample format '{sample_format}'"
|
|
))),
|
|
}
|
|
}
|
|
|
|
pub fn host_device_setup(
|
|
) -> Result<(cpal::Host, cpal::Device, cpal::SupportedStreamConfig), anyhow::Error> {
|
|
let host = cpal::default_host();
|
|
|
|
let device = host
|
|
.default_output_device()
|
|
.ok_or_else(|| anyhow::Error::msg("Default output device is not available"))?;
|
|
println!("Output device : {}", device.name()?);
|
|
|
|
let config = device.default_output_config()?;
|
|
println!("Default output config : {:?}", config);
|
|
|
|
Ok((host, device, config))
|
|
}
|
|
|
|
pub fn make_stream<T>(
|
|
device: &cpal::Device,
|
|
config: &cpal::StreamConfig,
|
|
) -> Result<cpal::Stream, anyhow::Error>
|
|
where
|
|
T: SizedSample + FromSample<f32>,
|
|
{
|
|
let num_channels = config.channels as usize;
|
|
let mut oscillator = Oscillator {
|
|
waveform: Waveform::Sine,
|
|
sample_rate: config.sample_rate.0 as f32,
|
|
current_sample_index: 0.0,
|
|
frequency_hz: 440.0,
|
|
};
|
|
let err_fn = |err| eprintln!("Error building output sound stream: {}", err);
|
|
|
|
let time_at_start = std::time::Instant::now();
|
|
println!("Time at start: {:?}", time_at_start);
|
|
|
|
let stream = device.build_output_stream(
|
|
config,
|
|
move |output: &mut [T], _: &cpal::OutputCallbackInfo| {
|
|
// for 0-1s play sine, 1-2s play square, 2-3s play saw, 3-4s play triangle_wave
|
|
let time_since_start = std::time::Instant::now()
|
|
.duration_since(time_at_start)
|
|
.as_secs_f32();
|
|
if time_since_start < 1.0 {
|
|
oscillator.set_waveform(Waveform::Sine);
|
|
} else if time_since_start < 2.0 {
|
|
oscillator.set_waveform(Waveform::Triangle);
|
|
} else if time_since_start < 3.0 {
|
|
oscillator.set_waveform(Waveform::Square);
|
|
} else if time_since_start < 4.0 {
|
|
oscillator.set_waveform(Waveform::Saw);
|
|
} else {
|
|
oscillator.set_waveform(Waveform::Sine);
|
|
}
|
|
process_frame(output, &mut oscillator, num_channels)
|
|
},
|
|
err_fn,
|
|
None,
|
|
)?;
|
|
|
|
Ok(stream)
|
|
}
|
|
|
|
fn process_frame<SampleType>(
|
|
output: &mut [SampleType],
|
|
oscillator: &mut Oscillator,
|
|
num_channels: usize,
|
|
) where
|
|
SampleType: Sample + FromSample<f32>,
|
|
{
|
|
for frame in output.chunks_mut(num_channels) {
|
|
let value: SampleType = SampleType::from_sample(oscillator.tick());
|
|
|
|
// copy the same value to all channels
|
|
for sample in frame.iter_mut() {
|
|
*sample = value;
|
|
}
|
|
}
|
|
}
|