use std::{
cmp::Ordering, collections::HashMap, fmt::Debug, ops::BitOr, sync::mpsc::{self, Receiver, Sender}
};
use cpal::{
traits::{DeviceTrait, HostTrait, StreamTrait},
Device, FromSample, OutputCallbackInfo, Sample, SampleFormat, SampleRate, SizedSample, Stream,
StreamConfig, SupportedStreamConfigRange,
};
#[derive(Debug)]
struct SynthBackend {
commands: Receiver<SynthCommand>,
sample_rate: u32,
channels: usize,
synths: HashMap<SynthId, SynthChannel>,
}
impl SynthBackend {
fn new(rx: Receiver<SynthCommand>, sample_rate: u32, channels: usize) -> SynthBackend {
SynthBackend {
commands: rx,
sample_rate,
channels,
synths: HashMap::new(),
}
}
fn write_samples<T: Sample + FromSample<f32>>(
&mut self,
data: &mut [T],
_info: &OutputCallbackInfo,
) {
for sample in data.iter_mut() {
*sample = T::EQUILIBRIUM;
}
while let Ok(cmd) = self.commands.try_recv() {
let synth = self
.synths
.entry(cmd.synth)
.or_insert_with(SynthChannel::default);
match cmd.operation {
SynthOp::SetVolume(vol) => synth.volume.tween_to(vol, cmd.lerp_time),
SynthOp::SetPitch(pitch) => synth.frequency.tween_to(pitch, cmd.lerp_time),
SynthOp::SetChannelMask(cm) => synth.channel_mask = cm,
}
}
let timestep = 1.0 / (self.sample_rate as f32);
for sample in 0..data.len() / self.channels {
let mut channels = vec![0.0; self.channels];
for synth in self.synths.values_mut() {
let val = synth.next_sample(timestep);
for i in 0..self.channels {
if synth.channel_mask.0 & (1 << i) != 0 {
channels[i] += val;
}
}
}
for (i, channel) in channels.into_iter().enumerate() {
data[sample * self.channels + i] = T::from_sample(channel);
}
}
}
}
#[derive(Debug, Clone)]
struct SynthChannel {
frequency: Tweenable,
volume: Tweenable,
channel_mask: ChannelMask,
oscillator: f32,
}
impl Default for SynthChannel {
fn default() -> Self {
Self {
frequency: Tweenable::new(220.0),
volume: Tweenable::new(0.0),
channel_mask: ChannelMask::STEREO,
oscillator: 0.0,
}
}
}
impl SynthChannel {
#[inline]
fn next_sample(&mut self, amount: f32) -> f32 {
let val = (self.oscillator * std::f32::consts::PI * 2.0).sin() * self.volume.current();
self.oscillator = (self.oscillator + amount * self.frequency.current()) % 1.0;
self.frequency.advance(amount);
self.volume.advance(amount);
val
}
}
#[derive(Debug, Clone)]
pub struct Tweenable {
current: f32,
tween: Option<(f32, f32)>,
}
impl Tweenable {
pub fn new(val: f32) -> Tweenable {
Tweenable {
current: val,
tween: None,
}
}
#[inline(always)]
pub fn current(&self) -> f32 {
self.current
}
#[inline]
pub fn advance(&mut self, amount: f32) -> f32 {
if let Some((target, remaining)) = self.tween {
if amount >= remaining {
self.current = target;
self.tween = None;
} else {
self.current += (target - self.current) * (amount / remaining);
self.tween = Some((target, remaining - amount));
}
}
self.current
}
#[inline]
pub fn tween_to(&mut self, target: f32, time: f32) {
self.tween = Some((target, time));
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct ChannelMask(pub usize);
impl ChannelMask {
pub const STEREO: Self = ChannelMask(3);
pub const LEFT: Self = ChannelMask(1 << 0);
pub const RIGHT: Self = ChannelMask(1 << 1);
pub const CENTER: Self = ChannelMask(1 << 2);
pub const SUB: Self = ChannelMask(1 << 3);
pub const REAR_LEFT: Self = ChannelMask(1 << 4);
pub const REAR_RIGHT: Self = ChannelMask(1 << 5);
pub const SIDE_LEFT: Self = ChannelMask(1 << 6);
pub const SIDE_RIGHT: Self = ChannelMask(1 << 7);
}
impl BitOr for ChannelMask {
type Output = usize;
fn bitor(self, rhs: Self) -> Self::Output {
self.0.bitor(rhs.0)
}
}
impl From<usize> for ChannelMask {
fn from(value: usize) -> Self {
ChannelMask(value)
}
}
impl Default for ChannelMask {
fn default() -> Self {
Self::STEREO
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SynthId(pub usize);
#[derive(Debug, Clone, Copy)]
enum SynthOp {
SetVolume(f32),
SetPitch(f32),
SetChannelMask(ChannelMask),
}
#[derive(Debug, Clone)]
struct SynthCommand {
synth: SynthId,
operation: SynthOp,
lerp_time: f32,
}
pub struct Synth {
_stream: Box<dyn StreamTrait>,
command_channel: Sender<SynthCommand>,
pub channel_count: usize,
pub next_synth: usize,
}
const DEFAULT_LERP_TIME: f32 = 0.01;
pub struct AudioSink {
pub host_name: String,
pub device_name: String,
pub device: Device,
}
impl Debug for AudioSink {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AudioSink")
.field("host_name", &self.host_name)
.field("device_name", &self.device_name)
.finish_non_exhaustive()
}
}
#[derive(Debug)]
pub struct SinkList(pub Vec<AudioSink>);
impl From<SinkList> for Device {
fn from(mut value: SinkList) -> Self {
value.0.remove(0).device
}
}
impl SinkList {
pub fn get_sinks() -> SinkList {
let mut list = Vec::new();
for host_id in cpal::available_hosts() {
let host = cpal::host_from_id(host_id).unwrap();
for device in host.output_devices().unwrap() {
list.push(AudioSink {
host_name: host_id.name().to_string(),
device_name: device.name().unwrap(),
device,
});
}
}
SinkList(list)
}
pub fn sort_default(mut self) -> Self {
self.0.sort_by_cached_key(|dev| {
-if dev.device_name.ends_with("_in") {
-10
} else if dev.host_name == "ALSA" {
match dev.device_name.as_str() {
"pipewire" => -1,
"jack" => -2,
"pulse" => -3,
"default" => -4,
_ => -5,
}
} else if dev.host_name == "JACK" && dev.device_name.ends_with("_out") {
1
} else {
0
}
});
self
}
}
fn device_sr(d: &SupportedStreamConfigRange) -> u32 {
48000.clamp(d.min_sample_rate().0, d.max_sample_rate().0)
}
fn sr_score(a: u32) -> i32 {
match a {
88200 | 96000 | 24000 | 22050 => 1,
44100..=48000 => 2,
0..=44099 => 96000 - (a as i32) * 2,
_ => 48000 - (a as i32),
}
}
fn fmt_score(a: SampleFormat) -> i32 {
match a {
SampleFormat::I8 => -4,
SampleFormat::I16 => -3,
SampleFormat::I32 => -2,
SampleFormat::I64 => -3,
SampleFormat::U8 => -4,
SampleFormat::U16 => -3,
SampleFormat::U32 => -2,
SampleFormat::U64 => -3,
SampleFormat::F32 => 0,
SampleFormat::F64 => -1,
_ => -10,
}
}
fn chan_score(a: u16) -> i32 {
match a {
2 => 14,
3..=13 => a as i32,
1 => 1,
_ => -(a as i32),
}
}
impl Synth {
pub fn new() -> Synth {
Synth::new_on_sink(Synth::get_sinks().sort_default())
}
pub fn get_sinks() -> SinkList {
SinkList::get_sinks()
}
pub fn new_on_sink(audio_sink: impl Into<Device>) -> Synth {
let device: Device = audio_sink.into();
let selected_config = device
.supported_output_configs()
.unwrap()
.reduce(|a, b| {
match sr_score(device_sr(&a)).cmp(&sr_score(device_sr(&b))) {
Ordering::Less => return b,
Ordering::Greater => return a,
_ => {}
}
match fmt_score(a.sample_format()).cmp(&fmt_score(b.sample_format())) {
Ordering::Less => return b,
Ordering::Greater => return a,
_ => {}
}
match chan_score(a.channels()).cmp(&chan_score(b.channels())) {
Ordering::Less => return b,
Ordering::Greater => return a,
_ => {}
}
a
})
.unwrap();
let sr = device_sr(&selected_config);
let selected_config = selected_config.with_sample_rate(SampleRate(sr));
fn build_stream<T: SizedSample + FromSample<f32>>(
device: &Device,
config: &StreamConfig,
mut backend: SynthBackend,
) -> Stream {
let err_fn = |e| eprintln!("CPAL Error: {e}");
let stream = device
.build_output_stream(
config,
move |data, info| backend.write_samples::<T>(data, info),
err_fn,
None,
)
.unwrap();
stream.play().unwrap();
stream
}
let (tx, rx) = mpsc::channel();
let sample_rate = selected_config.sample_rate();
let sample_format = selected_config.sample_format();
let channel_count = selected_config.channels() as usize;
let backend = SynthBackend::new(rx, sample_rate.0, channel_count);
let stream = {
let config = selected_config.into();
match sample_format {
SampleFormat::I8 => build_stream::<i8>(&device, &config, backend),
SampleFormat::I16 => build_stream::<i16>(&device, &config, backend),
SampleFormat::I32 => build_stream::<i32>(&device, &config, backend),
SampleFormat::I64 => build_stream::<i64>(&device, &config, backend),
SampleFormat::U8 => build_stream::<u8>(&device, &config, backend),
SampleFormat::U16 => build_stream::<u16>(&device, &config, backend),
SampleFormat::U32 => build_stream::<u32>(&device, &config, backend),
SampleFormat::U64 => build_stream::<u64>(&device, &config, backend),
SampleFormat::F32 => build_stream::<f32>(&device, &config, backend),
SampleFormat::F64 => build_stream::<f64>(&device, &config, backend),
_ => todo!(),
}
};
Synth {
_stream: Box::new(stream),
command_channel: tx,
channel_count,
next_synth: 0,
}
}
pub fn new_synth(&mut self, volume: f32, pitch: f32) -> SynthId {
let synth = SynthId(self.next_synth);
self.next_synth += 1;
self.set_volume(synth, volume);
self.set_pitch(synth, pitch);
self.set_channel_mask(synth, ChannelMask::default());
synth
}
pub fn set_volume(&mut self, synth: SynthId, volume: f32) {
self.set_volume_lerp(synth, volume, DEFAULT_LERP_TIME);
}
pub fn set_volume_lerp(&mut self, synth: SynthId, volume: f32, lerp_time: f32) {
self.command_channel
.send(SynthCommand {
synth,
operation: SynthOp::SetVolume(volume),
lerp_time,
})
.unwrap();
}
pub fn set_pitch(&mut self, synth: SynthId, pitch: f32) {
self.set_pitch_lerp(synth, pitch, DEFAULT_LERP_TIME);
}
pub fn set_pitch_lerp(&mut self, synth: SynthId, pitch: f32, lerp_time: f32) {
self.command_channel
.send(SynthCommand {
synth,
operation: SynthOp::SetPitch(pitch),
lerp_time,
})
.unwrap();
}
pub fn set_channel_mask(&mut self, synth: SynthId, channel_mask: ChannelMask) {
self.set_channel_mask_lerp(synth, channel_mask, DEFAULT_LERP_TIME);
}
pub fn set_channel_mask_lerp(
&mut self,
synth: SynthId,
channel_mask: ChannelMask,
lerp_time: f32,
) {
self.command_channel
.send(SynthCommand {
synth,
operation: SynthOp::SetChannelMask(channel_mask),
lerp_time,
})
.unwrap();
}
pub fn stop_all(&mut self) {
for s in 0..self.next_synth {
self.set_volume(SynthId(s), 0.0);
}
}
}