1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
use crate::config::AudioFormat;
use crate::convert::Converter;
use crate::decoder::AudioPacket;
use thiserror::Error;

#[derive(Debug, Error)]
pub enum SinkError {
    #[error("Audio Sink Error Not Connected: {0}")]
    NotConnected(String),
    #[error("Audio Sink Error Connection Refused: {0}")]
    ConnectionRefused(String),
    #[error("Audio Sink Error On Write: {0}")]
    OnWrite(String),
    #[error("Audio Sink Error Invalid Parameters: {0}")]
    InvalidParams(String),
}

pub type SinkResult<T> = Result<T, SinkError>;

pub trait Open {
    fn open(_: Option<String>, format: AudioFormat) -> Self;
}

pub trait Sink {
    fn start(&mut self) -> SinkResult<()> {
        Ok(())
    }
    fn stop(&mut self) -> SinkResult<()> {
        Ok(())
    }
    fn write(&mut self, packet: AudioPacket, converter: &mut Converter) -> SinkResult<()>;
}

pub type SinkBuilder = fn(Option<String>, AudioFormat) -> Box<dyn Sink>;

pub trait SinkAsBytes {
    fn write_bytes(&mut self, data: &[u8]) -> SinkResult<()>;
}

fn mk_sink<S: Sink + Open + 'static>(device: Option<String>, format: AudioFormat) -> Box<dyn Sink> {
    Box::new(S::open(device, format))
}

// reuse code for various backends
macro_rules! sink_as_bytes {
    () => {
        fn write(&mut self, packet: AudioPacket, converter: &mut Converter) -> SinkResult<()> {
            use crate::convert::i24;
            use zerocopy::AsBytes;
            match packet {
                AudioPacket::Samples(samples) => match self.format {
                    AudioFormat::F64 => self.write_bytes(samples.as_bytes()),
                    AudioFormat::F32 => {
                        let samples_f32: &[f32] = &converter.f64_to_f32(&samples);
                        self.write_bytes(samples_f32.as_bytes())
                    }
                    AudioFormat::S32 => {
                        let samples_s32: &[i32] = &converter.f64_to_s32(&samples);
                        self.write_bytes(samples_s32.as_bytes())
                    }
                    AudioFormat::S24 => {
                        let samples_s24: &[i32] = &converter.f64_to_s24(&samples);
                        self.write_bytes(samples_s24.as_bytes())
                    }
                    AudioFormat::S24_3 => {
                        let samples_s24_3: &[i24] = &converter.f64_to_s24_3(&samples);
                        self.write_bytes(samples_s24_3.as_bytes())
                    }
                    AudioFormat::S16 => {
                        let samples_s16: &[i16] = &converter.f64_to_s16(&samples);
                        self.write_bytes(samples_s16.as_bytes())
                    }
                },
                AudioPacket::OggData(samples) => self.write_bytes(&samples),
            }
        }
    };
}

#[cfg(feature = "alsa-backend")]
mod alsa;
#[cfg(feature = "alsa-backend")]
use self::alsa::AlsaSink;

#[cfg(feature = "portaudio-backend")]
mod portaudio;
#[cfg(feature = "portaudio-backend")]
use self::portaudio::PortAudioSink;

#[cfg(feature = "pulseaudio-backend")]
mod pulseaudio;
#[cfg(feature = "pulseaudio-backend")]
use self::pulseaudio::PulseAudioSink;

#[cfg(feature = "jackaudio-backend")]
mod jackaudio;
#[cfg(feature = "jackaudio-backend")]
use self::jackaudio::JackSink;

#[cfg(feature = "gstreamer-backend")]
mod gstreamer;
#[cfg(feature = "gstreamer-backend")]
use self::gstreamer::GstreamerSink;

#[cfg(any(feature = "rodio-backend", feature = "rodiojack-backend"))]
mod rodio;
#[cfg(feature = "rodio-backend")]
use self::rodio::RodioSink;

#[cfg(feature = "sdl-backend")]
mod sdl;
#[cfg(feature = "sdl-backend")]
use self::sdl::SdlSink;

mod pipe;
use self::pipe::StdoutSink;

mod subprocess;
use self::subprocess::SubprocessSink;

pub const BACKENDS: &[(&str, SinkBuilder)] = &[
    #[cfg(feature = "rodio-backend")]
    (RodioSink::NAME, rodio::mk_rodio), // default goes first
    #[cfg(feature = "alsa-backend")]
    (AlsaSink::NAME, mk_sink::<AlsaSink>),
    #[cfg(feature = "portaudio-backend")]
    (PortAudioSink::NAME, mk_sink::<PortAudioSink>),
    #[cfg(feature = "pulseaudio-backend")]
    (PulseAudioSink::NAME, mk_sink::<PulseAudioSink>),
    #[cfg(feature = "jackaudio-backend")]
    (JackSink::NAME, mk_sink::<JackSink>),
    #[cfg(feature = "gstreamer-backend")]
    (GstreamerSink::NAME, mk_sink::<GstreamerSink>),
    #[cfg(feature = "rodiojack-backend")]
    ("rodiojack", rodio::mk_rodiojack),
    #[cfg(feature = "sdl-backend")]
    (SdlSink::NAME, mk_sink::<SdlSink>),
    (StdoutSink::NAME, mk_sink::<StdoutSink>),
    (SubprocessSink::NAME, mk_sink::<SubprocessSink>),
];

pub fn find(name: Option<String>) -> Option<SinkBuilder> {
    if let Some(name) = name {
        BACKENDS
            .iter()
            .find(|backend| name == backend.0)
            .map(|backend| backend.1)
    } else {
        BACKENDS.first().map(|backend| backend.1)
    }
}