5 mpsc
::{Receiver
, SyncSender
, sync_channel
},
7 time
::{Duration
, Instant
},
11 BuildStreamError
, Device
, DevicesError
, InputCallbackInfo
, OutputCallbackInfo
, PlayStreamError
,
12 SampleFormat
, SampleRate
, Stream
, StreamError
, SupportedStreamConfig
,
13 SupportedStreamConfigRange
, SupportedStreamConfigsError
,
14 traits
::{DeviceTrait
, HostTrait
, StreamTrait
},
16 use num_traits
::{NumCast
, WrappingNeg
};
19 use crate::soundmodem
::{
20 InputSource
, OutputBuffer
, OutputSink
, SoundmodemErrorSender
, SoundmodemEvent
,
23 /// A soundcard for used for transmitting/receiving baseband with a `Soundmodem`.
25 /// Use `input()` and `output()` to retrieve source/sink handles for the soundmodem.
26 /// It is fine to use an input from one soundcard and and output from another.
28 /// If you try to create more than one `Soundcard` instance at a time for the same card
29 /// then it may not work.
30 pub struct Soundcard
{
31 event_tx
: SyncSender
<SoundcardEvent
>,
35 pub fn new
<S
: Into
<String
>>(card_name
: S
) -> Result
<Self, SoundcardError
> {
36 let (card_tx
, card_rx
) = sync_channel(128);
37 let (setup_tx
, setup_rx
) = sync_channel(1);
38 spawn_soundcard_worker(card_rx
, setup_tx
, card_name
.into
());
39 match setup_rx
.recv() {
40 Ok(Ok(())) => Ok(Self { event_tx
: card_tx
}),
42 Err(_
) => Err(SoundcardError
::SoundcardInit
),
46 pub fn input(&self) -> SoundcardInputSource
{
47 SoundcardInputSource
{
48 event_tx
: self.event_tx
.clone(),
52 pub fn output(&self) -> SoundcardOutputSink
{
54 event_tx
: self.event_tx
.clone(),
58 pub fn set_rx_inverted(&self, inverted
: bool
) {
59 let _
= self.event_tx
.send(SoundcardEvent
::SetRxInverted(inverted
));
62 pub fn set_tx_inverted(&self, inverted
: bool
) {
63 let _
= self.event_tx
.send(SoundcardEvent
::SetTxInverted(inverted
));
66 /// List soundcards supported for soundmodem output.
68 /// Today, this requires support for a 48kHz sample rate.
69 pub fn supported_output_cards() -> Vec
<String
> {
71 let host
= cpal
::default_host();
72 let Ok(output_devices
) = host
.output_devices() else {
75 for d
in output_devices
{
76 let Ok(mut configs
) = d
.supported_output_configs() else {
79 if configs
.any(config_is_compatible
) {
80 let Ok(name
) = d
.name() else {
90 /// List soundcards supported for soundmodem input.
92 /// Today, this requires support for a 48kHz sample rate.
93 pub fn supported_input_cards() -> Vec
<String
> {
95 let host
= cpal
::default_host();
96 let Ok(input_devices
) = host
.inp
ut
_dev
ices
() else {
99 for d
in input_devices
{
100 let Ok(mut configs
) = d
.supported_input_configs() else {
103 if configs
.any(config_is_compatible
) {
104 let Ok(name
) = d
.name() else {
115 fn config_is_compatible
<C
: Borrow
<SupportedStreamConfigRange
>>(config
: C
) -> bool
{
116 let config
= config
.borrow();
117 (config
.channels() == 1 || config
.channels() == 2)
118 && (config
.sample_format() == SampleFormat
::I16
119 || config
.sample_format() == SampleFormat
::I32
)
120 && config
.min_sample_rate().0 <= 48000
121 && config
.max_sample_rate().0 >= 48000
124 enum SoundcardEvent
{
128 samples
: SyncSender
<SoundmodemEvent
>,
129 errors
: SoundmodemErrorSender
,
133 event_tx
: SyncSender
<SoundmodemEvent
>,
134 buffer
: Arc
<RwLock
<OutputBuffer
>>,
135 errors
: SoundmodemErrorSender
,
140 pub struct SoundcardInputSource
{
141 event_tx
: SyncSender
<SoundcardEvent
>,
144 impl InputSource
for SoundcardInputSource
{
145 fn start(&self, samples
: SyncSender
<SoundmodemEvent
>, errors
: SoundmodemErrorSender
) {
148 .send(SoundcardEvent
::StartInput
{ samples
, errors
});
152 let _
= self.event_tx
.send(SoundcardEvent
::CloseInput
);
156 pub struct SoundcardOutputSink
{
157 event_tx
: SyncSender
<SoundcardEvent
>,
160 impl OutputSink
for SoundcardOutputSink
{
163 event_tx
: SyncSender
<SoundmodemEvent
>,
164 buffer
: Arc
<RwLock
<OutputBuffer
>>,
165 errors
: SoundmodemErrorSender
,
167 let _
= self.event_tx
.send(SoundcardEvent
::StartOutput
{
175 let _
= self.event_tx
.send(SoundcardEvent
::CloseOutput
);
179 fn build_input_cb
<T
: NumCast
+ WrappingNeg
+ Clone
>(
180 samples
: SyncSender
<SoundmodemEvent
>,
183 ) -> impl Fn(&[T
], &InputCallbackInfo
) {
184 move |data
: &[T
], _info
: &cpal
::InputCallbackInfo
| {
185 let mut out
= vec
![];
186 for d
in data
.chunks(channels
as usize) {
187 // if we were given multi-channel input we'll pick the first channel
188 let mut sample
= d
[0].clone();
190 sample
= sample
.wrapping_neg();
192 out
.push(NumCast
::from(sample
).unwrap
());
194 let _
= samples
.try_send(SoundmodemEvent
::BasebandInput(out
.into
()));
198 fn build_input_stream(
200 input_config
: SupportedStreamConfig
,
201 errors
: SoundmodemErrorSender
,
202 samples
: SyncSender
<SoundmodemEvent
>,
205 ) -> Result
<Stream
, BuildStreamError
> {
206 if input_config
.sample_format() == SampleFormat
::I16
{
207 device
.build_input_stream(
208 &input_config
.into
(),
209 build_input_cb
::<i16>(samples
, channels
, rx_inverted
),
211 errors
.send_error(SoundcardError
::Stream(e
));
216 device
.build_input_stream(
217 &input_config
.into
(),
218 build_input_cb
::<i32>(samples
, channels
, rx_inverted
),
220 errors
.send_error(SoundcardError
::Stream(e
));
227 fn build_output_cb
<T
: NumCast
+ WrappingNeg
+ Clone
>(
228 event_tx
: SyncSender
<SoundmodemEvent
>,
231 buffer
: Arc
<RwLock
<OutputBuffer
>>,
232 ) -> impl Fn(&mut [T
], &OutputCallbackInfo
) {
233 move |data
: &mut [T
], info
: &cpal
::OutputCallbackInfo
| {
235 let ts
= info
.timestamp();
238 .duration_since(&ts
.callback
)
239 .unwrap
_or
(Duration
::ZERO
);
240 let mut buffer
= buffer
.write().unwrap
();
241 buffer
.latency
= latency
;
242 for out
in data
.chunks_mut(channels
as usize) {
243 if let Some(s
) = buffer
.samples
.pop_front() {
244 out
.fill
(NumCast
::from(if tx_inverted
{ s
.saturating_neg() } else { s
}).unwrap
());
246 } else if buffer
.idl
ing
{
247 out
.fill
(NumCast
::from(0).unwrap
());
249 let _
= event_tx
.send(SoundmodemEvent
::OutputUnderrun
);
253 let _
= event_tx
.send(SoundmodemEvent
::DidReadFromOutputBuffer
{
255 timestamp
: Instant
::now(),
260 fn build_output_stream(
262 output_config
: SupportedStreamConfig
,
263 errors
: SoundmodemErrorSender
,
264 event_tx
: SyncSender
<SoundmodemEvent
>,
267 buffer
: Arc
<RwLock
<OutputBuffer
>>,
268 ) -> Result
<Stream
, BuildStreamError
> {
269 if output_config
.sample_format() == SampleFormat
::I16
{
270 device
.build_output_stream(
271 &output_config
.into
(),
272 build_output_cb
::<i16>(event_tx
, channels
, tx_inverted
, buffer
),
274 errors
.send_error(SoundcardError
::Stream(e
));
279 device
.build_output_stream(
280 &output_config
.into
(),
281 build_output_cb
::<i32>(event_tx
, channels
, tx_inverted
, buffer
),
283 errors
.send_error(SoundcardError
::Stream(e
));
290 fn spawn_soundcard_worker(
291 event_rx
: Receiver
<SoundcardEvent
>,
292 setup_tx
: SyncSender
<Result
<(), SoundcardError
>>,
295 std
::thread
::spawn(move || {
296 let host
= cpal
::default_host();
297 let Some(device
) = host
300 .find
(|d
| d
.name().unwrap
() == card_name
)
302 let _
= setup_tx
.send(Err(SoundcardError
::CardNotFound(card_name
)));
306 let _
= setup_tx
.send(Ok(()));
307 let mut rx_inverted
= false;
308 let mut tx_inverted
= false;
309 let mut input_stream
: Option
<Stream
> = None
;
310 let mut output_stream
: Option
<Stream
> = None
;
312 while let Ok(ev
) = event_rx
.recv() {
314 SoundcardEvent
::SetRxInverted(inv
) => rx_inverted
= inv
,
315 SoundcardEvent
::SetTxInverted(inv
) => tx_inverted
= inv
,
316 SoundcardEvent
::StartInput
{ samples
, errors
} => {
317 let mut input_configs
= match device
.supported_input_configs() {
320 errors
.send_error(SoundcardError
::SupportedConfigs(e
));
324 let input_config
= match input_configs
.find
(|c
| config_is_compatible(c
)) {
327 errors
.send_error(SoundcardError
::NoValidConfigAvailable
);
331 let input_config
= input_config
.with_sample_rate(SampleRate(48000));
332 let channels
= input_config
.channels();
333 let stream
= match build_input_stream(
343 errors
.send_error(SoundcardError
::StreamBuild(e
));
347 if let Err(e
) = stream
.play() {
348 errors
.send_error(SoundcardError
::StreamPlay(e
));
351 input_stream
= Some(stream
);
353 SoundcardEvent
::CloseInput
=> {
354 let _
= input_stream
.take();
356 SoundcardEvent
::StartOutput
{
361 let mut output_configs
= match device
.supported_output_configs() {
364 errors
.send_error(SoundcardError
::SupportedConfigs(e
));
368 let output_config
= match output_configs
.find
(|c
| config_is_compatible(c
)) {
371 errors
.send_error(SoundcardError
::NoValidConfigAvailable
);
375 let output_config
= output_config
.with_sample_rate(SampleRate(48000));
376 let channels
= output_config
.channels();
377 let stream
= match build_output_stream(
388 errors
.send_error(SoundcardError
::StreamBuild(e
));
392 if let Err(e
) = stream
.play() {
393 errors
.send_error(SoundcardError
::StreamPlay(e
));
396 output_stream
= Some(stream
);
398 SoundcardEvent
::CloseOutput
=> {
399 let _
= output_stream
.take();
406 #[derive(Debug, Error)]
407 pub enum SoundcardError
{
408 #[error("sound card init aborted unexpectedly")]
411 #[error("unable to enumerate devices: {0}")]
414 #[error("unable to locate sound card '{0}' - is it in use?")]
415 CardNotFound(String
),
417 #[error("error occurred in soundcard i/o: {0}")]
418 Stream(#[source] StreamError),
420 #[error("unable to retrieve supported configs for soundcard: {0}")]
421 SupportedConfigs(#[source] SupportedStreamConfigsError),
423 #[error("could not find a suitable soundcard config")]
424 NoValidConfigAvailable
,
426 #[error("unable to build soundcard stream: {0}")]
427 StreamBuild(#[source] BuildStreamError),
429 #[error("unable to play stream")]
430 StreamPlay(#[source] PlayStreamError),