]> code.octet-stream.net Git - m17rt/blob - m17app/src/soundcard.rs
Add error handling
[m17rt] / m17app / src / soundcard.rs
1 use std::{
2 sync::{
3 mpsc::{sync_channel, Receiver, SyncSender},
4 Arc, RwLock,
5 },
6 time::{Duration, Instant},
7 };
8
9 use cpal::{
10 traits::{DeviceTrait, HostTrait, StreamTrait},
11 SampleFormat, SampleRate, Stream,
12 };
13
14 use crate::{
15 error::{M17Error, SoundmodemError},
16 soundmodem::{InputSource, OutputBuffer, OutputSink, SoundmodemEvent},
17 };
18
19 pub struct Soundcard {
20 event_tx: SyncSender<SoundcardEvent>,
21 }
22
23 impl Soundcard {
24 pub fn new<S: Into<String>>(card_name: S) -> Result<Self, M17Error> {
25 let (card_tx, card_rx) = sync_channel(128);
26 let (setup_tx, setup_rx) = sync_channel(1);
27 spawn_soundcard_worker(card_rx, setup_tx, card_name.into());
28 match setup_rx.recv() {
29 Ok(Ok(())) => Ok(Self { event_tx: card_tx }),
30 Ok(Err(e)) => Err(e),
31 Err(_) => Err(M17Error::SoundcardInit),
32 }
33 }
34
35 pub fn input(&self) -> SoundcardInputSource {
36 SoundcardInputSource {
37 event_tx: self.event_tx.clone(),
38 }
39 }
40
41 pub fn output(&self) -> SoundcardOutputSink {
42 SoundcardOutputSink {
43 event_tx: self.event_tx.clone(),
44 }
45 }
46
47 pub fn set_rx_inverted(&self, inverted: bool) {
48 let _ = self.event_tx.send(SoundcardEvent::SetRxInverted(inverted));
49 }
50
51 pub fn set_tx_inverted(&self, inverted: bool) {
52 let _ = self.event_tx.send(SoundcardEvent::SetTxInverted(inverted));
53 }
54
55 pub fn supported_output_cards() -> Vec<String> {
56 let mut out = vec![];
57 let host = cpal::default_host();
58 let Ok(output_devices) = host.output_devices() else {
59 return out;
60 };
61 for d in output_devices {
62 let Ok(mut configs) = d.supported_output_configs() else {
63 continue;
64 };
65 if configs
66 .any(|config| config.channels() == 1 && config.sample_format() == SampleFormat::I16)
67 {
68 let Ok(name) = d.name() else {
69 continue;
70 };
71 out.push(name);
72 }
73 }
74 out.sort();
75 out
76 }
77
78 pub fn supported_input_cards() -> Vec<String> {
79 let mut out = vec![];
80 let host = cpal::default_host();
81 let Ok(input_devices) = host.input_devices() else {
82 return out;
83 };
84 for d in input_devices {
85 let Ok(mut configs) = d.supported_input_configs() else {
86 continue;
87 };
88 if configs
89 .any(|config| config.channels() == 1 && config.sample_format() == SampleFormat::I16)
90 {
91 let Ok(name) = d.name() else {
92 continue;
93 };
94 out.push(name);
95 }
96 }
97 out.sort();
98 out
99 }
100 }
101
102 enum SoundcardEvent {
103 SetRxInverted(bool),
104 SetTxInverted(bool),
105 StartInput {
106 samples: SyncSender<SoundmodemEvent>,
107 },
108 CloseInput,
109 StartOutput {
110 event_tx: SyncSender<SoundmodemEvent>,
111 buffer: Arc<RwLock<OutputBuffer>>,
112 },
113 CloseOutput,
114 }
115
116 pub struct SoundcardInputSource {
117 event_tx: SyncSender<SoundcardEvent>,
118 }
119
120 impl InputSource for SoundcardInputSource {
121 fn start(&self, samples: SyncSender<SoundmodemEvent>) -> Result<(), SoundmodemError> {
122 Ok(self.event_tx.send(SoundcardEvent::StartInput { samples })?)
123 }
124
125 fn close(&self) -> Result<(), SoundmodemError> {
126 Ok(self.event_tx.send(SoundcardEvent::CloseInput)?)
127 }
128 }
129
130 pub struct SoundcardOutputSink {
131 event_tx: SyncSender<SoundcardEvent>,
132 }
133
134 impl OutputSink for SoundcardOutputSink {
135 fn start(
136 &self,
137 event_tx: SyncSender<SoundmodemEvent>,
138 buffer: Arc<RwLock<OutputBuffer>>,
139 ) -> Result<(), SoundmodemError> {
140 Ok(self
141 .event_tx
142 .send(SoundcardEvent::StartOutput { event_tx, buffer })?)
143 }
144
145 fn close(&self) -> Result<(), SoundmodemError> {
146 Ok(self.event_tx.send(SoundcardEvent::CloseOutput)?)
147 }
148 }
149
150 fn spawn_soundcard_worker(
151 event_rx: Receiver<SoundcardEvent>,
152 setup_tx: SyncSender<Result<(), M17Error>>,
153 card_name: String,
154 ) {
155 std::thread::spawn(move || {
156 let host = cpal::default_host();
157 let Some(device) = host
158 .devices()
159 .unwrap()
160 .find(|d| d.name().unwrap() == card_name)
161 else {
162 let _ = setup_tx.send(Err(M17Error::SoundcardNotFound(card_name)));
163 return;
164 };
165
166 let _ = setup_tx.send(Ok(()));
167 let mut rx_inverted = false;
168 let mut tx_inverted = false;
169 let mut input_stream: Option<Stream> = None;
170 let mut output_stream: Option<Stream> = None;
171
172 while let Ok(ev) = event_rx.recv() {
173 match ev {
174 SoundcardEvent::SetRxInverted(inv) => rx_inverted = inv,
175 SoundcardEvent::SetTxInverted(inv) => tx_inverted = inv,
176 SoundcardEvent::StartInput { samples } => {
177 let mut input_configs = device.supported_input_configs().unwrap();
178 let input_config = input_configs
179 .find(|c| c.channels() == 1 && c.sample_format() == SampleFormat::I16)
180 .unwrap()
181 .with_sample_rate(SampleRate(48000));
182 let stream = device
183 .build_input_stream(
184 &input_config.into(),
185 move |data: &[i16], _info: &cpal::InputCallbackInfo| {
186 let out: Vec<i16> = data
187 .iter()
188 .map(|s| if rx_inverted { s.saturating_neg() } else { *s })
189 .collect();
190 let _ =
191 samples.try_send(SoundmodemEvent::BasebandInput(out.into()));
192 },
193 |e| {
194 // TODO: abort?
195 log::debug!("error occurred in soundcard input: {e:?}");
196 },
197 None,
198 )
199 .unwrap();
200 stream.play().unwrap();
201 input_stream = Some(stream);
202 }
203 SoundcardEvent::CloseInput => {
204 let _ = input_stream.take();
205 }
206 SoundcardEvent::StartOutput { event_tx, buffer } => {
207 let mut output_configs = device.supported_output_configs().unwrap();
208 // TODO: more error handling
209 let output_config = output_configs
210 .find(|c| c.channels() == 1 && c.sample_format() == SampleFormat::I16)
211 .unwrap()
212 .with_sample_rate(SampleRate(48000));
213 let stream = device
214 .build_output_stream(
215 &output_config.into(),
216 move |data: &mut [i16], info: &cpal::OutputCallbackInfo| {
217 let mut taken = 0;
218 let ts = info.timestamp();
219 let latency = ts
220 .playback
221 .duration_since(&ts.callback)
222 .unwrap_or(Duration::ZERO);
223 let mut buffer = buffer.write().unwrap();
224 buffer.latency = latency;
225 for out in data.iter_mut() {
226 if let Some(s) = buffer.samples.pop_front() {
227 *out = if tx_inverted { s.saturating_neg() } else { s };
228 taken += 1;
229 } else if buffer.idling {
230 *out = 0;
231 } else {
232 log::debug!("output soundcard had underrun");
233 let _ = event_tx.send(SoundmodemEvent::OutputUnderrun);
234 break;
235 }
236 }
237 //debug!("latency is {} ms, taken {taken}", latency.as_millis());
238 let _ = event_tx.send(SoundmodemEvent::DidReadFromOutputBuffer {
239 len: taken,
240 timestamp: Instant::now(),
241 });
242 },
243 |e| {
244 // TODO: abort?
245 log::debug!("error occurred in soundcard output: {e:?}");
246 },
247 None,
248 )
249 .unwrap();
250 stream.play().unwrap();
251 output_stream = Some(stream);
252 }
253 SoundcardEvent::CloseOutput => {
254 let _ = output_stream.take();
255 }
256 }
257 }
258 });
259 }