vedasynth/
lib.rs

1use std::{
2    cmp::Ordering, collections::HashMap, fmt::Debug, ops::BitOr, sync::mpsc::{self, Receiver, Sender}
3};
4
5use cpal::{
6    traits::{DeviceTrait, HostTrait, StreamTrait},
7    Device, FromSample, OutputCallbackInfo, Sample, SampleFormat, SampleRate, SizedSample, Stream,
8    StreamConfig, SupportedStreamConfigRange,
9};
10
11#[derive(Debug)]
12struct SynthBackend {
13    commands: Receiver<SynthCommand>,
14    sample_rate: u32,
15    channels: usize,
16    synths: HashMap<SynthId, SynthChannel>,
17}
18
19impl SynthBackend {
20    fn new(rx: Receiver<SynthCommand>, sample_rate: u32, channels: usize) -> SynthBackend {
21        SynthBackend {
22            commands: rx,
23            sample_rate,
24            channels,
25            synths: HashMap::new(),
26        }
27    }
28    fn write_samples<T: Sample + FromSample<f32>>(
29        &mut self,
30        data: &mut [T],
31        _info: &OutputCallbackInfo,
32    ) {
33        for sample in data.iter_mut() {
34            *sample = T::EQUILIBRIUM;
35        }
36        while let Ok(cmd) = self.commands.try_recv() {
37            let synth = self
38                .synths
39                .entry(cmd.synth)
40                .or_insert_with(SynthChannel::default);
41            match cmd.operation {
42                SynthOp::SetVolume(vol) => synth.volume.tween_to(vol, cmd.lerp_time),
43                SynthOp::SetPitch(pitch) => synth.frequency.tween_to(pitch, cmd.lerp_time),
44                SynthOp::SetChannelMask(cm) => synth.channel_mask = cm,
45            }
46        }
47        let timestep = 1.0 / (self.sample_rate as f32);
48        /*if let Some(s) = self.synths.get(&SynthId(0)) {
49            let o2 = s.oscillator + timestep*s.frequency.current();
50            println!("O: {}, O2: {o2}, Dif: {}", s.oscillator, o2-s.oscillator);
51        }*/
52        for sample in 0..data.len() / self.channels {
53            let mut channels = vec![0.0; self.channels];
54            for synth in self.synths.values_mut() {
55                let val = synth.next_sample(timestep);
56                for i in 0..self.channels {
57                    if synth.channel_mask.0 & (1 << i) != 0 {
58                        channels[i] += val;
59                    }
60                }
61            }
62            for (i, channel) in channels.into_iter().enumerate() {
63                data[sample * self.channels + i] = T::from_sample(channel);
64            }
65        }
66    }
67}
68
69#[derive(Debug, Clone)]
70struct SynthChannel {
71    frequency: Tweenable,
72    volume: Tweenable,
73    channel_mask: ChannelMask,
74    oscillator: f32,
75}
76
77impl Default for SynthChannel {
78    fn default() -> Self {
79        Self {
80            frequency: Tweenable::new(220.0),
81            volume: Tweenable::new(0.0),
82            channel_mask: ChannelMask::STEREO,
83            oscillator: 0.0,
84        }
85    }
86}
87
88impl SynthChannel {
89    #[inline]
90    fn next_sample(&mut self, amount: f32) -> f32 {
91        let val = (self.oscillator * std::f32::consts::PI * 2.0).sin() * self.volume.current();
92        self.oscillator = (self.oscillator + amount * self.frequency.current()) % 1.0;
93        self.frequency.advance(amount);
94        self.volume.advance(amount);
95        val
96    }
97}
98
99// TODO: Make this support more than linear interpolations
100#[derive(Debug, Clone)]
101pub struct Tweenable {
102    current: f32,
103    tween: Option<(f32, f32)>,
104}
105
106impl Tweenable {
107    pub fn new(val: f32) -> Tweenable {
108        Tweenable {
109            current: val,
110            tween: None,
111        }
112    }
113    #[inline(always)]
114    pub fn current(&self) -> f32 {
115        self.current
116    }
117    #[inline]
118    pub fn advance(&mut self, amount: f32) -> f32 {
119        if let Some((target, remaining)) = self.tween {
120            if amount >= remaining {
121                self.current = target;
122                self.tween = None;
123            } else {
124                self.current += (target - self.current) * (amount / remaining);
125                self.tween = Some((target, remaining - amount));
126            }
127        }
128        self.current
129    }
130    #[inline]
131    pub fn tween_to(&mut self, target: f32, time: f32) {
132        self.tween = Some((target, time));
133    }
134}
135
136#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
137pub struct ChannelMask(pub usize);
138
139impl ChannelMask {
140    pub const STEREO: Self = ChannelMask(3);
141    pub const LEFT: Self = ChannelMask(1 << 0);
142    pub const RIGHT: Self = ChannelMask(1 << 1);
143    pub const CENTER: Self = ChannelMask(1 << 2);
144    pub const SUB: Self = ChannelMask(1 << 3);
145    pub const REAR_LEFT: Self = ChannelMask(1 << 4);
146    pub const REAR_RIGHT: Self = ChannelMask(1 << 5);
147    pub const SIDE_LEFT: Self = ChannelMask(1 << 6);
148    pub const SIDE_RIGHT: Self = ChannelMask(1 << 7);
149}
150
151impl BitOr for ChannelMask {
152    type Output = usize;
153
154    fn bitor(self, rhs: Self) -> Self::Output {
155        self.0.bitor(rhs.0)
156    }
157}
158
159impl From<usize> for ChannelMask {
160    fn from(value: usize) -> Self {
161        ChannelMask(value)
162    }
163}
164
165impl Default for ChannelMask {
166    fn default() -> Self {
167        Self::STEREO
168    }
169}
170
171#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
172pub struct SynthId(pub usize);
173
174#[derive(Debug, Clone, Copy)]
175enum SynthOp {
176    SetVolume(f32),
177    SetPitch(f32),
178    SetChannelMask(ChannelMask),
179}
180
181#[derive(Debug, Clone)]
182struct SynthCommand {
183    synth: SynthId,
184    operation: SynthOp,
185    lerp_time: f32,
186}
187
188pub struct Synth {
189    _stream: Box<dyn StreamTrait>,
190    command_channel: Sender<SynthCommand>,
191    pub channel_count: usize,
192    pub next_synth: usize,
193}
194
195const DEFAULT_LERP_TIME: f32 = 0.01;
196
197pub struct AudioSink {
198    pub host_name: String,
199    pub device_name: String,
200    pub device: Device,
201}
202
203impl Debug for AudioSink {
204    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205        f.debug_struct("AudioSink")
206            .field("host_name", &self.host_name)
207            .field("device_name", &self.device_name)
208            .finish_non_exhaustive()
209    }
210}
211
212#[derive(Debug)]
213pub struct SinkList(pub Vec<AudioSink>);
214
215impl From<SinkList> for Device {
216    fn from(mut value: SinkList) -> Self {
217        value.0.remove(0).device
218    }
219}
220
221impl SinkList {
222    pub fn get_sinks() -> SinkList {
223        let mut list = Vec::new();
224        for host_id in cpal::available_hosts() {
225            let host = cpal::host_from_id(host_id).unwrap();
226            for device in host.output_devices().unwrap() {
227                list.push(AudioSink {
228                    host_name: host_id.name().to_string(),
229                    device_name: device.name().unwrap(),
230                    device,
231                });
232            }
233        }
234        SinkList(list)
235    }
236    pub fn sort_default(mut self) -> Self {
237        self.0.sort_by_cached_key(|dev| {
238            -if dev.device_name.ends_with("_in") {
239                -10
240            } else if dev.host_name == "ALSA" {
241                match dev.device_name.as_str() {
242                    "pipewire" => -1,
243                    "jack" => -2,
244                    "pulse" => -3,
245                    "default" => -4,
246                    _ => -5,
247                }
248            } else if dev.host_name == "JACK" && dev.device_name.ends_with("_out") {
249                1
250            } else {
251                0
252            }
253        });
254        self
255    }
256}
257
258fn device_sr(d: &SupportedStreamConfigRange) -> u32 {
259    48000.clamp(d.min_sample_rate().0, d.max_sample_rate().0)
260}
261
262fn sr_score(a: u32) -> i32 {
263    match a {
264        88200 | 96000 | 24000 | 22050 => 1,
265        44100..=48000 => 2,
266        0..=44099 => 96000 - (a as i32) * 2,
267        _ => 48000 - (a as i32),
268    }
269}
270fn fmt_score(a: SampleFormat) -> i32 {
271    match a {
272        SampleFormat::I8 => -4,
273        SampleFormat::I16 => -3,
274        SampleFormat::I32 => -2,
275        SampleFormat::I64 => -3,
276        SampleFormat::U8 => -4,
277        SampleFormat::U16 => -3,
278        SampleFormat::U32 => -2,
279        SampleFormat::U64 => -3,
280        SampleFormat::F32 => 0,
281        SampleFormat::F64 => -1,
282        _ => -10,
283    }
284}
285fn chan_score(a: u16) -> i32 {
286    match a {
287        2 => 14,
288        3..=13 => a as i32,
289        1 => 1,
290        _ => -(a as i32),
291    }
292}
293
294impl Synth {
295    pub fn new() -> Synth {
296        Synth::new_on_sink(Synth::get_sinks().sort_default())
297    }
298    pub fn get_sinks() -> SinkList {
299        SinkList::get_sinks()
300    }
301    pub fn new_on_sink(audio_sink: impl Into<Device>) -> Synth {
302        let device: Device = audio_sink.into();
303        let selected_config = device
304            .supported_output_configs()
305            .unwrap()
306            .reduce(|a, b| {
307                match sr_score(device_sr(&a)).cmp(&sr_score(device_sr(&b))) {
308                    Ordering::Less => return b,
309                    Ordering::Greater => return a,
310                    _ => {}
311                }
312                match fmt_score(a.sample_format()).cmp(&fmt_score(b.sample_format())) {
313                    Ordering::Less => return b,
314                    Ordering::Greater => return a,
315                    _ => {}
316                }
317                match chan_score(a.channels()).cmp(&chan_score(b.channels())) {
318                    Ordering::Less => return b,
319                    Ordering::Greater => return a,
320                    _ => {}
321                }
322
323                a
324            })
325            .unwrap();
326        let sr = device_sr(&selected_config);
327        let selected_config = selected_config.with_sample_rate(SampleRate(sr));
328        fn build_stream<T: SizedSample + FromSample<f32>>(
329            device: &Device,
330            config: &StreamConfig,
331            mut backend: SynthBackend,
332        ) -> Stream {
333            let err_fn = |e| eprintln!("CPAL Error: {e}");
334            let stream = device
335                .build_output_stream(
336                    config,
337                    move |data, info| backend.write_samples::<T>(data, info),
338                    err_fn,
339                    None,
340                )
341                .unwrap();
342            stream.play().unwrap();
343            stream
344        }
345        let (tx, rx) = mpsc::channel();
346        let sample_rate = selected_config.sample_rate();
347        let sample_format = selected_config.sample_format();
348        let channel_count = selected_config.channels() as usize;
349        let backend = SynthBackend::new(rx, sample_rate.0, channel_count);
350        let stream = {
351            let config = selected_config.into();
352            match sample_format {
353                SampleFormat::I8 => build_stream::<i8>(&device, &config, backend),
354                SampleFormat::I16 => build_stream::<i16>(&device, &config, backend),
355                SampleFormat::I32 => build_stream::<i32>(&device, &config, backend),
356                SampleFormat::I64 => build_stream::<i64>(&device, &config, backend),
357                SampleFormat::U8 => build_stream::<u8>(&device, &config, backend),
358                SampleFormat::U16 => build_stream::<u16>(&device, &config, backend),
359                SampleFormat::U32 => build_stream::<u32>(&device, &config, backend),
360                SampleFormat::U64 => build_stream::<u64>(&device, &config, backend),
361                SampleFormat::F32 => build_stream::<f32>(&device, &config, backend),
362                SampleFormat::F64 => build_stream::<f64>(&device, &config, backend),
363                _ => todo!(),
364            }
365        };
366        Synth {
367            _stream: Box::new(stream),
368            command_channel: tx,
369            channel_count,
370            next_synth: 0,
371        }
372    }
373    pub fn new_synth(&mut self, volume: f32, pitch: f32) -> SynthId {
374        let synth = SynthId(self.next_synth);
375        self.next_synth += 1;
376        self.set_volume(synth, volume);
377        self.set_pitch(synth, pitch);
378        self.set_channel_mask(synth, ChannelMask::default());
379        synth
380    }
381    pub fn set_volume(&mut self, synth: SynthId, volume: f32) {
382        self.set_volume_lerp(synth, volume, DEFAULT_LERP_TIME);
383    }
384    pub fn set_volume_lerp(&mut self, synth: SynthId, volume: f32, lerp_time: f32) {
385        self.command_channel
386            .send(SynthCommand {
387                synth,
388                operation: SynthOp::SetVolume(volume),
389                lerp_time,
390            })
391            .unwrap();
392    }
393    pub fn set_pitch(&mut self, synth: SynthId, pitch: f32) {
394        self.set_pitch_lerp(synth, pitch, DEFAULT_LERP_TIME);
395    }
396    pub fn set_pitch_lerp(&mut self, synth: SynthId, pitch: f32, lerp_time: f32) {
397        self.command_channel
398            .send(SynthCommand {
399                synth,
400                operation: SynthOp::SetPitch(pitch),
401                lerp_time,
402            })
403            .unwrap();
404    }
405    pub fn set_channel_mask(&mut self, synth: SynthId, channel_mask: ChannelMask) {
406        self.set_channel_mask_lerp(synth, channel_mask, DEFAULT_LERP_TIME);
407    }
408    pub fn set_channel_mask_lerp(
409        &mut self,
410        synth: SynthId,
411        channel_mask: ChannelMask,
412        lerp_time: f32,
413    ) {
414        self.command_channel
415            .send(SynthCommand {
416                synth,
417                operation: SynthOp::SetChannelMask(channel_mask),
418                lerp_time,
419            })
420            .unwrap();
421    }
422    pub fn stop_all(&mut self) {
423        for s in 0..self.next_synth {
424            self.set_volume(SynthId(s), 0.0);
425        }
426    }
427}