1use iced::{
2 Element,
3 widget::{button, checkbox, column, pick_list, row, text},
4};
5
6pub const DEFAULT_N_PERIODS: usize = 2;
8
9#[derive(Debug, Clone)]
11pub struct AudioSetupState<Backend, Device> {
12 pub backends: Vec<Backend>,
13 pub selected_backend: Backend,
14 pub show_input_device: bool,
15 pub input_devices: Vec<Device>,
16 pub selected_input_device: Option<Device>,
17 pub show_output_device: bool,
18 pub output_devices: Vec<Device>,
19 pub selected_output_device: Option<Device>,
20 pub show_sample_rate: bool,
21 pub sample_rates: Vec<i32>,
22 pub selected_sample_rate: Option<i32>,
23 pub show_bit_depth: bool,
24 pub bit_depths: Vec<usize>,
25 pub selected_bit_depth: Option<usize>,
26 pub show_period_frames: bool,
27 pub period_frames: Vec<usize>,
28 pub selected_period_frames: Option<usize>,
29 pub show_n_periods: bool,
30 pub n_periods: Vec<usize>,
31 pub selected_n_periods: Option<usize>,
32 pub show_exclusive: bool,
33 pub exclusive: bool,
34 pub show_sync_mode: bool,
35 pub sync_mode: bool,
36 pub plugins_loaded: bool,
37 pub can_start: bool,
38 pub status_message: String,
39}
40
41#[derive(Debug, Clone)]
43pub enum AudioSetupAction<Backend, Device> {
44 BackendSelected(Backend),
45 InputDeviceSelected(Device),
46 OutputDeviceSelected(Device),
47 SampleRateSelected(i32),
48 BitDepthSelected(usize),
49 PeriodFramesSelected(usize),
50 NPeriodsSelected(usize),
51 ExclusiveToggled(bool),
52 SyncModeToggled(bool),
53 Start,
54}
55
56pub fn audio_setup<Backend, Device, Message>(
58 state: AudioSetupState<Backend, Device>,
59 on_action: impl Fn(AudioSetupAction<Backend, Device>) -> Message + Clone + 'static,
60) -> Element<'static, Message>
61where
62 Backend: Clone + PartialEq + std::fmt::Display + 'static,
63 Device: Clone + PartialEq + std::fmt::Display + 'static,
64 Message: Clone + 'static,
65{
66 let AudioSetupState {
67 backends,
68 selected_backend,
69 show_input_device,
70 input_devices,
71 selected_input_device,
72 show_output_device,
73 output_devices,
74 selected_output_device,
75 show_sample_rate,
76 sample_rates,
77 selected_sample_rate,
78 show_bit_depth,
79 bit_depths,
80 selected_bit_depth,
81 show_period_frames,
82 period_frames,
83 selected_period_frames,
84 show_n_periods,
85 n_periods,
86 selected_n_periods,
87 show_exclusive,
88 exclusive,
89 show_sync_mode,
90 sync_mode,
91 plugins_loaded: _,
92 can_start,
93 status_message,
94 } = state;
95
96 let mut content = column![pick_row(
97 "Backend:",
98 backends,
99 Some(selected_backend),
100 {
101 let on_action = on_action.clone();
102 move |b| on_action(AudioSetupAction::BackendSelected(b))
103 },
104 "Choose backend",
105 )]
106 .spacing(10);
107
108 if show_input_device {
109 content = content.push(pick_row(
110 "Input device:",
111 input_devices,
112 selected_input_device,
113 {
114 let on_action = on_action.clone();
115 move |d| on_action(AudioSetupAction::InputDeviceSelected(d))
116 },
117 "Choose input device",
118 ));
119 }
120
121 if show_output_device {
122 content = content.push(pick_row(
123 "Output device:",
124 output_devices,
125 selected_output_device,
126 {
127 let on_action = on_action.clone();
128 move |d| on_action(AudioSetupAction::OutputDeviceSelected(d))
129 },
130 "Choose output device",
131 ));
132 }
133
134 if show_sample_rate {
135 content = content.push(pick_row(
136 "Sample rate (Hz):",
137 sample_rates,
138 selected_sample_rate,
139 {
140 let on_action = on_action.clone();
141 move |r| on_action(AudioSetupAction::SampleRateSelected(r))
142 },
143 "Choose sample rate",
144 ));
145 }
146
147 if show_bit_depth {
148 content = content.push(pick_row(
149 "Bit depth:",
150 bit_depths,
151 selected_bit_depth,
152 {
153 let on_action = on_action.clone();
154 move |b| on_action(AudioSetupAction::BitDepthSelected(b))
155 },
156 "Bit depth",
157 ));
158 }
159
160 if show_period_frames {
161 let latency_text = match (
162 selected_period_frames,
163 selected_sample_rate,
164 selected_n_periods,
165 ) {
166 (Some(frames), Some(rate), Some(nperiods)) if rate > 0 => {
167 let latency_ms = (frames as f64 * nperiods as f64 * 1000.0) / rate as f64;
168 text(format!("{latency_ms:.1} ms"))
169 }
170 _ => text(""),
171 };
172 content = content.push(
173 row![
174 text("Period frames:"),
175 pick_list(period_frames, selected_period_frames, {
176 let on_action = on_action.clone();
177 move |v| on_action(AudioSetupAction::PeriodFramesSelected(v))
178 },)
179 .placeholder("Period"),
180 latency_text,
181 ]
182 .spacing(10),
183 );
184 }
185
186 if show_n_periods {
187 content = content.push(pick_row(
188 "N periods:",
189 n_periods,
190 selected_n_periods,
191 {
192 let on_action = on_action.clone();
193 move |n| on_action(AudioSetupAction::NPeriodsSelected(n))
194 },
195 "N periods",
196 ));
197 }
198
199 if show_exclusive {
200 content = content.push(checkbox(exclusive).label("Exclusive mode").on_toggle({
201 let on_action = on_action.clone();
202 move |v| on_action(AudioSetupAction::ExclusiveToggled(v))
203 }));
204 }
205
206 if show_sync_mode {
207 content = content.push(checkbox(sync_mode).label("Sync mode").on_toggle({
208 let on_action = on_action.clone();
209 move |v| on_action(AudioSetupAction::SyncModeToggled(v))
210 }));
211 }
212
213 let mut submit = button("Open Audio");
214 if can_start {
215 submit = submit.on_press(on_action(AudioSetupAction::Start));
216 }
217 content = content.push(submit);
218
219 if !status_message.is_empty() {
220 content = content.push(text(status_message).size(12));
221 }
222
223 content.into()
224}
225
226fn pick_row<'a, T, Message>(
227 label: &'static str,
228 options: Vec<T>,
229 selected: Option<T>,
230 on_select: impl Fn(T) -> Message + 'a,
231 placeholder: &'static str,
232) -> Element<'a, Message>
233where
234 T: Clone + PartialEq + std::fmt::Display + 'a,
235 Message: Clone + 'a,
236{
237 row![
238 text(label),
239 pick_list(options, selected, on_select).placeholder(placeholder)
240 ]
241 .spacing(10)
242 .into()
243}