diff --git a/Cargo.toml b/Cargo.toml index d800231..6d0b111 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,3 +13,12 @@ path = "src/lib.rs" [[bin]] path = "src/main.rs" name = "raust" + +[dependencies] +anyhow = "1.0.104" +cpal = { version = "0.18.2", features = ["realtime"] } + +[features] +default = [] + +pipewire = ["cpal/pipewire"] diff --git a/devenv.nix b/devenv.nix index 6b94466..2a55255 100644 --- a/devenv.nix +++ b/devenv.nix @@ -2,8 +2,16 @@ packages = with pkgs; [ tombi bacon + + pkg-config + alsa-lib.dev + pipewire + pipewire.dev + llvmPackages.libclang ]; + env.LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib"; + languages.rust = { enable = true; toolchainFile = ./rust-toolchain.toml; diff --git a/src/lib.rs b/src/lib.rs index 6eec06a..49dfc7d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,98 @@ +use cpal::{ + Device, Error, ErrorKind, FromSample, HostId, I24, OutputCallbackInfo, Sample, SampleFormat, + SizedSample, StreamConfig, + traits::{DeviceTrait, HostTrait, StreamTrait}, +}; + pub fn add(a: u32, b: u32) -> u32 { a + b } + +pub fn synthesize() -> anyhow::Result<()> { + #[allow(unused_mut, unused_assignments)] + let mut pipewire_host_id: Result = Err(ErrorKind::HostUnavailable.into()); + #[cfg(any( + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + ))] + { + #[cfg(feature = "pipewire")] + { + pipewire_host_id = Ok(HostId::PipeWire); + } + } + let host = pipewire_host_id + .and_then(cpal::host_from_id) + .unwrap_or_else(|_| cpal::default_host()); + let device = host.default_output_device().expect("Failed to find output device"); + println!("Output device: {}", device.id()?); + let config = device.default_output_config()?; + println!("Default output config: {config:?}"); + match config.sample_format() { + SampleFormat::I8 => run::(&device, config.into()), + SampleFormat::I16 => run::(&device, config.into()), + SampleFormat::I24 => run::(&device, config.into()), + SampleFormat::I32 => run::(&device, config.into()), + // SampleFormat::I48 => run::(&device, config.into()), + SampleFormat::I64 => run::(&device, config.into()), + SampleFormat::U8 => run::(&device, config.into()), + SampleFormat::U16 => run::(&device, config.into()), + // SampleFormat::U24 => run::(&device, config.into()), + SampleFormat::U32 => run::(&device, config.into()), + // SampleFormat::U48 => run::(&device, config.into()), + SampleFormat::U64 => run::(&device, config.into()), + SampleFormat::F32 => run::(&device, config.into()), + SampleFormat::F64 => run::(&device, config.into()), + sample_format => panic!("Unsupported sample format '{sample_format}'"), + } +} + +pub fn run(device: &Device, config: StreamConfig) -> Result<(), anyhow::Error> +where + T: SizedSample + FromSample, +{ + let sample_rate = config.sample_rate as f32; + let channels = config.channels as usize; + + // Produce a sinusoid of maximum amplitude. + let mut sample_clock = 0f32; + let mut next_value = move || { + sample_clock = (sample_clock + 1.0) % sample_rate; + (sample_clock * 440.0 * 2.0 * std::f32::consts::PI / sample_rate).sin() + }; + + let err_fn = |err: Error| match err.kind() { + ErrorKind::DeviceChanged | ErrorKind::RealtimeDenied => { + eprintln!("{err}") + } + _ => eprintln!("Stream error: {err}"), + }; + + let stream = device.build_output_stream( + config, + move |data: &mut [T], _: &OutputCallbackInfo| write_data(data, channels, &mut next_value), + err_fn, + None, + )?; + stream.play()?; + + std::thread::sleep(std::time::Duration::from_millis(1000)); + + stream.pause()?; + + Ok(()) +} + +fn write_data(output: &mut [T], channels: usize, next_sample: &mut dyn FnMut() -> f32) +where + T: Sample + FromSample, +{ + for frame in output.chunks_mut(channels) { + let value: T = T::from_sample(next_sample()); + for sample in frame.iter_mut() { + *sample = value; + } + } +} diff --git a/src/main.rs b/src/main.rs index 3a6446c..3048d7c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ -use raust::add; +use raust::synthesize; fn main() { - println!("Hello, world! {}", add(1, 2)); + synthesize().expect("Failed to run synthesis engine"); }