Skip to main content

maolan_widgets/
audio_setup.rs

1use iced::{
2    Element,
3    widget::{button, checkbox, column, pick_list, row, text},
4};
5
6/// Default number of periods used by the shared audio hardware setup screen.
7pub const DEFAULT_N_PERIODS: usize = 2;
8
9/// State required to render the shared audio hardware setup screen.
10#[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/// Every user action that the shared audio setup screen can produce.
42#[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
56/// Renders the shared audio hardware setup screen.
57pub 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}