5 mpsc
::{Receiver
, SyncSender
, sync_channel
},
7 time
::{Duration
, Instant
},
11 BuildStreamError
, DevicesError
, PlayStreamError
, SampleFormat
, SampleRate
, Stream
, StreamError
,
12 SupportedStreamConfigRange
, SupportedStreamConfigsError
,
13 traits
::{DeviceTrait
, HostTrait
, StreamTrait
},
17 use crate::soundmodem
::{
18 InputSource
, OutputBuffer
, OutputSink
, SoundmodemErrorSender
, SoundmodemEvent
,
21 /// A soundcard for used for transmitting/receiving baseband with a `Soundmodem`.
23 /// Use `input()` and `output()` to retrieve source/sink handles for the soundmodem.
24 /// It is fine to use an input from one soundcard and and output from another.
26 /// If you try to create more than one `Soundcard` instance at a time for the same card
27 /// then it may not work.
28 pub struct Soundcard
{
29 event_tx
: SyncSender
<SoundcardEvent
>,
33 pub fn new
<S
: Into
<String
>>(card_name
: S
) -> Result
<Self, SoundcardError
> {
34 let (card_tx
, card_rx
) = sync_channel(128);
35 let (setup_tx
, setup_rx
) = sync_channel(1);
36 spawn_soundcard_worker(card_rx
, setup_tx
, card_name
.into
());
37 match setup_rx
.recv() {
38 Ok(Ok(())) => Ok(Self { event_tx
: card_tx
}),
40 Err(_
) => Err(SoundcardError
::SoundcardInit
),
44 pub fn input(&self) -> SoundcardInputSource
{
45 SoundcardInputSource
{
46 event_tx
: self.event_tx
.clone(),
50 pub fn output(&self) -> SoundcardOutputSink
{
52 event_tx
: self.event_tx
.clone(),
56 pub fn set_rx_inverted(&self, inverted
: bool
) {
57 let _
= self.event_tx
.send(SoundcardEvent
::SetRxInverted(inverted
));
60 pub fn set_tx_inverted(&self, inverted
: bool
) {
61 let _
= self.event_tx
.send(SoundcardEvent
::SetTxInverted(inverted
));
64 /// List soundcards supported for soundmodem output.
66 /// Today, this requires support for a 48kHz sample rate.
67 pub fn supported_output_cards() -> Vec
<String
> {
69 let host
= cpal
::default_host();
70 let Ok(output_devices
) = host
.output_devices() else {
73 for d
in output_devices
{
74 let Ok(mut configs
) = d
.supported_output_configs() else {
77 if configs
.any(config_is_compatible
) {
78 let Ok(name
) = d
.name() else {
88 /// List soundcards supported for soundmodem input.
90 /// Today, this requires support for a 48kHz sample rate.
91 pub fn supported_input_cards() -> Vec
<String
> {
93 let host
= cpal
::default_host();
94 let Ok(input_devices
) = host
.inp
ut
_dev
ices
() else {
97 for d
in input_devices
{
98 let Ok(mut configs
) = d
.supported_input_configs() else {
101 if configs
.any(config_is_compatible
) {
102 let Ok(name
) = d
.name() else {
113 fn config_is_compatible
<C
: Borrow
<SupportedStreamConfigRange
>>(config
: C
) -> bool
{
114 let config
= config
.borrow();
115 (config
.channels() == 1 || config
.channels() == 2)
116 && config
.sample_format() == SampleFormat
::I16
117 && config
.min_sample_rate().0 <= 48000
118 && config
.max_sample_rate().0 >= 48000
121 enum SoundcardEvent
{
125 samples
: SyncSender
<SoundmodemEvent
>,
126 errors
: SoundmodemErrorSender
,
130 event_tx
: SyncSender
<SoundmodemEvent
>,
131 buffer
: Arc
<RwLock
<OutputBuffer
>>,
132 errors
: SoundmodemErrorSender
,
137 pub struct SoundcardInputSource
{
138 event_tx
: SyncSender
<SoundcardEvent
>,
141 impl InputSource
for SoundcardInputSource
{
142 fn start(&self, samples
: SyncSender
<SoundmodemEvent
>, errors
: SoundmodemErrorSender
) {
145 .send(SoundcardEvent
::StartInput
{ samples
, errors
});
149 let _
= self.event_tx
.send(SoundcardEvent
::CloseInput
);
153 pub struct SoundcardOutputSink
{
154 event_tx
: SyncSender
<SoundcardEvent
>,
157 impl OutputSink
for SoundcardOutputSink
{
160 event_tx
: SyncSender
<SoundmodemEvent
>,
161 buffer
: Arc
<RwLock
<OutputBuffer
>>,
162 errors
: SoundmodemErrorSender
,
164 let _
= self.event_tx
.send(SoundcardEvent
::StartOutput
{
172 let _
= self.event_tx
.send(SoundcardEvent
::CloseOutput
);
176 fn spawn_soundcard_worker(
177 event_rx
: Receiver
<SoundcardEvent
>,
178 setup_tx
: SyncSender
<Result
<(), SoundcardError
>>,
181 std
::thread
::spawn(move || {
182 let host
= cpal
::default_host();
183 let Some(device
) = host
186 .find
(|d
| d
.name().unwrap
() == card_name
)
188 let _
= setup_tx
.send(Err(SoundcardError
::CardNotFound(card_name
)));
192 let _
= setup_tx
.send(Ok(()));
193 let mut rx_inverted
= false;
194 let mut tx_inverted
= false;
195 let mut input_stream
: Option
<Stream
> = None
;
196 let mut output_stream
: Option
<Stream
> = None
;
198 while let Ok(ev
) = event_rx
.recv() {
200 SoundcardEvent
::SetRxInverted(inv
) => rx_inverted
= inv
,
201 SoundcardEvent
::SetTxInverted(inv
) => tx_inverted
= inv
,
202 SoundcardEvent
::StartInput
{ samples
, errors
} => {
203 let mut input_configs
= match device
.supported_input_configs() {
206 errors
.send_error(SoundcardError
::SupportedConfigs(e
));
210 let input_config
= match input_configs
.find
(|c
| config_is_compatible(c
)) {
213 errors
.send_error(SoundcardError
::NoValidConfigAvailable
);
217 let input_config
= input_config
.with_sample_rate(SampleRate(48000));
218 let channels
= input_config
.channels();
219 let errors_1
= errors
.clone();
220 let stream
= match device
.build_input_stream(
221 &input_config
.into
(),
222 move |data
: &[i16], _info
: &cpal
::InputCallbackInfo
| {
223 let mut out
= vec
![];
224 for d
in data
.chunks(channels
as usize) {
225 // if we were given multi-channel input we'll pick the first channel
226 let mut sample
= d
[0];
228 sample
= sample
.saturating_neg();
232 let _
= samples
.try_send(SoundmodemEvent
::BasebandInput(out
.into
()));
235 errors_1
.send_error(SoundcardError
::Stream(e
));
241 errors
.send_error(SoundcardError
::StreamBuild(e
));
245 if let Err(e
) = stream
.play() {
246 errors
.send_error(SoundcardError
::StreamPlay(e
));
249 input_stream
= Some(stream
);
251 SoundcardEvent
::CloseInput
=> {
252 let _
= input_stream
.take();
254 SoundcardEvent
::StartOutput
{
259 let mut output_configs
= match device
.supported_output_configs() {
262 errors
.send_error(SoundcardError
::SupportedConfigs(e
));
266 let output_config
= match output_configs
.find
(|c
| config_is_compatible(c
)) {
269 errors
.send_error(SoundcardError
::NoValidConfigAvailable
);
273 let output_config
= output_config
.with_sample_rate(SampleRate(48000));
274 let channels
= output_config
.channels();
275 let errors_1
= errors
.clone();
276 let stream
= match device
.build_output_stream(
277 &output_config
.into
(),
278 move |data
: &mut [i16], info
: &cpal
::OutputCallbackInfo
| {
280 let ts
= info
.timestamp();
283 .duration_since(&ts
.callback
)
284 .unwrap
_or
(Duration
::ZERO
);
285 let mut buffer
= buffer
.write().unwrap
();
286 buffer
.latency
= latency
;
287 for out
in data
.chunks_mut(channels
as usize) {
288 if let Some(s
) = buffer
.samples
.pop_front() {
289 out
.fill
(if tx_inverted
{ s
.saturating_neg() } else { s
});
291 } else if buffer
.idl
ing
{
294 let _
= event_tx
.send(SoundmodemEvent
::OutputUnderrun
);
298 let _
= event_tx
.send(SoundmodemEvent
::DidReadFromOutputBuffer
{
300 timestamp
: Instant
::now(),
304 errors_1
.send_error(SoundcardError
::Stream(e
));
310 errors
.send_error(SoundcardError
::StreamBuild(e
));
314 if let Err(e
) = stream
.play() {
315 errors
.send_error(SoundcardError
::StreamPlay(e
));
318 output_stream
= Some(stream
);
320 SoundcardEvent
::CloseOutput
=> {
321 let _
= output_stream
.take();
328 #[derive(Debug, Error)]
329 pub enum SoundcardError
{
330 #[error("sound card init aborted unexpectedly")]
333 #[error("unable to enumerate devices: {0}")]
336 #[error("unable to locate sound card '{0}' - is it in use?")]
337 CardNotFound(String
),
339 #[error("error occurred in soundcard i/o: {0}")]
340 Stream(#[source] StreamError),
342 #[error("unable to retrieve supported configs for soundcard: {0}")]
343 SupportedConfigs(#[source] SupportedStreamConfigsError),
345 #[error("could not find a suitable soundcard config")]
346 NoValidConfigAvailable
,
348 #[error("unable to build soundcard stream: {0}")]
349 StreamBuild(#[source] BuildStreamError),
351 #[error("unable to play stream")]
352 StreamPlay(#[source] PlayStreamError),