]> code.octet-stream.net Git - m17rt/blob - m17app/src/soundcard.rs
New Soundcard struct, use it in test programs
[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,
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 .find(|config| {
67 config.channels() == 1 && config.sample_format() == SampleFormat::I16
68 })
69 .is_some()
70 {
71 let Ok(name) = d.name() else {
72 continue;
73 };
74 out.push(name);
75 }
76 }
77 out.sort();
78 out
79 }
80
81 pub fn supported_input_cards() -> Vec<String> {
82 let mut out = vec![];
83 let host = cpal::default_host();
84 let Ok(input_devices) = host.input_devices() else {
85 return out;
86 };
87 for d in input_devices {
88 let Ok(mut configs) = d.supported_input_configs() else {
89 continue;
90 };
91 if configs
92 .find(|config| {
93 config.channels() == 1 && config.sample_format() == SampleFormat::I16
94 })
95 .is_some()
96 {
97 let Ok(name) = d.name() else {
98 continue;
99 };
100 out.push(name);
101 }
102 }
103 out.sort();
104 out
105 }
106 }
107
108 enum SoundcardEvent {
109 SetRxInverted(bool),
110 SetTxInverted(bool),
111 StartInput {
112 samples: SyncSender<SoundmodemEvent>,
113 },
114 CloseInput,
115 StartOutput {
116 event_tx: SyncSender<SoundmodemEvent>,
117 buffer: Arc<RwLock<OutputBuffer>>,
118 },
119 CloseOutput,
120 }
121
122 pub struct SoundcardInputSource {
123 event_tx: SyncSender<SoundcardEvent>,
124 }
125
126 impl InputSource for SoundcardInputSource {
127 fn start(&self, samples: SyncSender<SoundmodemEvent>) {
128 let _ = self.event_tx.send(SoundcardEvent::StartInput { samples });
129 }
130
131 fn close(&self) {
132 let _ = self.event_tx.send(SoundcardEvent::CloseInput);
133 }
134 }
135
136 pub struct SoundcardOutputSink {
137 event_tx: SyncSender<SoundcardEvent>,
138 }
139
140 impl OutputSink for SoundcardOutputSink {
141 fn start(&self, event_tx: SyncSender<SoundmodemEvent>, buffer: Arc<RwLock<OutputBuffer>>) {
142 let _ = self
143 .event_tx
144 .send(SoundcardEvent::StartOutput { event_tx, buffer });
145 }
146
147 fn close(&self) {
148 let _ = self.event_tx.send(SoundcardEvent::CloseOutput);
149 }
150 }
151
152 fn spawn_soundcard_worker(
153 event_rx: Receiver<SoundcardEvent>,
154 setup_tx: SyncSender<Result<(), M17Error>>,
155 card_name: String,
156 ) {
157 std::thread::spawn(move || {
158 let host = cpal::default_host();
159 let Some(device) = host
160 .devices()
161 .unwrap()
162 .find(|d| d.name().unwrap() == card_name)
163 else {
164 let _ = setup_tx.send(Err(M17Error::SoundcardNotFound(card_name)));
165 return;
166 };
167
168 let _ = setup_tx.send(Ok(()));
169 let mut rx_inverted = false;
170 let mut tx_inverted = false;
171 let mut input_stream: Option<Stream> = None;
172 let mut output_stream: Option<Stream> = None;
173
174 while let Ok(ev) = event_rx.recv() {
175 match ev {
176 SoundcardEvent::SetRxInverted(inv) => rx_inverted = inv,
177 SoundcardEvent::SetTxInverted(inv) => tx_inverted = inv,
178 SoundcardEvent::StartInput { samples } => {
179 let mut input_configs = device.supported_input_configs().unwrap();
180 let input_config = input_configs
181 .find(|c| c.channels() == 1 && c.sample_format() == SampleFormat::I16)
182 .unwrap()
183 .with_sample_rate(SampleRate(48000));
184 let stream = device
185 .build_input_stream(
186 &input_config.into(),
187 move |data: &[i16], _info: &cpal::InputCallbackInfo| {
188 let out: Vec<i16> = data
189 .iter()
190 .map(|s| if rx_inverted { s.saturating_neg() } else { *s })
191 .collect();
192 let _ =
193 samples.try_send(SoundmodemEvent::BasebandInput(out.into()));
194 },
195 |e| {
196 // TODO: abort?
197 log::debug!("error occurred in soundcard input: {e:?}");
198 },
199 None,
200 )
201 .unwrap();
202 stream.play().unwrap();
203 input_stream = Some(stream);
204 }
205 SoundcardEvent::CloseInput => {
206 let _ = input_stream.take();
207 }
208 SoundcardEvent::StartOutput { event_tx, buffer } => {
209 let mut output_configs = device.supported_output_configs().unwrap();
210 // TODO: more error handling
211 let output_config = output_configs
212 .find(|c| c.channels() == 1 && c.sample_format() == SampleFormat::I16)
213 .unwrap()
214 .with_sample_rate(SampleRate(48000));
215 let stream = device
216 .build_output_stream(
217 &output_config.into(),
218 move |data: &mut [i16], info: &cpal::OutputCallbackInfo| {
219 let mut taken = 0;
220 let ts = info.timestamp();
221 let latency = ts
222 .playback
223 .duration_since(&ts.callback)
224 .unwrap_or(Duration::ZERO);
225 let mut buffer = buffer.write().unwrap();
226 buffer.latency = latency;
227 for out in data.iter_mut() {
228 if let Some(s) = buffer.samples.pop_front() {
229 *out = if tx_inverted { s.saturating_neg() } else { s };
230 taken += 1;
231 } else if buffer.idling {
232 *out = 0;
233 } else {
234 log::debug!("output soundcard had underrun");
235 let _ = event_tx.send(SoundmodemEvent::OutputUnderrun);
236 break;
237 }
238 }
239 //debug!("latency is {} ms, taken {taken}", latency.as_millis());
240 let _ = event_tx.send(SoundmodemEvent::DidReadFromOutputBuffer {
241 len: taken,
242 timestamp: Instant::now(),
243 });
244 },
245 |e| {
246 // TODO: abort?
247 log::debug!("error occurred in soundcard output: {e:?}");
248 },
249 None,
250 )
251 .unwrap();
252 stream.play().unwrap();
253 output_stream = Some(stream);
254 }
255 SoundcardEvent::CloseOutput => {
256 let _ = output_stream.take();
257 }
258 }
259 }
260 });
261 }