Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T03:42:58.059Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

audio_test_window.rs

308 lines · 9.9 KB · rust
1use audio::{AudioSettings, CHANNEL_COUNT, RodioExt, SAMPLE_RATE};
2use cpal::DeviceId;
3use gpui::{
4    App, Context, Entity, FocusHandle, Focusable, Render, Size, Window, WindowBounds, WindowKind,
5    WindowOptions, prelude::*, px,
6};
7use platform_title_bar::PlatformTitleBar;
8use release_channel::ReleaseChannel;
9use rodio::Source;
10use settings::{AudioInputDeviceName, AudioOutputDeviceName, Settings};
11use std::{
12    any::Any,
13    sync::{
14        Arc,
15        atomic::{AtomicBool, Ordering},
16    },
17    thread,
18    time::Duration,
19};
20use ui::{Button, ButtonStyle, Label, prelude::*};
21use util::ResultExt;
22use workspace::client_side_decorations;
23
24use super::audio_input_output_setup::render_audio_device_dropdown;
25use crate::{SettingsUiFile, update_settings_file};
26
27pub struct AudioTestWindow {
28    title_bar: Option<Entity<PlatformTitleBar>>,
29    input_device_id: Option<DeviceId>,
30    output_device_id: Option<DeviceId>,
31    focus_handle: FocusHandle,
32    _stop_playback: Option<Box<dyn Any + Send>>,
33}
34
35impl AudioTestWindow {
36    pub fn new(cx: &mut Context<Self>) -> Self {
37        let title_bar = if !cfg!(target_os = "macos") {
38            Some(cx.new(|cx| PlatformTitleBar::new("audio-test-title-bar", cx)))
39        } else {
40            None
41        };
42
43        let audio_settings = AudioSettings::get_global(cx);
44        let input_device_id = audio_settings.input_audio_device.clone();
45        let output_device_id = audio_settings.output_audio_device.clone();
46
47        Self {
48            title_bar,
49            input_device_id,
50            output_device_id,
51            focus_handle: cx.focus_handle(),
52            _stop_playback: None,
53        }
54    }
55
56    fn toggle_testing(&mut self, cx: &mut Context<Self>) {
57        if let Some(_cb) = self._stop_playback.take() {
58            cx.notify();
59            return;
60        }
61
62        if let Some(cb) =
63            start_test_playback(self.input_device_id.clone(), self.output_device_id.clone()).ok()
64        {
65            self._stop_playback = Some(cb);
66        }
67
68        cx.notify();
69    }
70}
71
72fn start_test_playback(
73    input_device_id: Option<DeviceId>,
74    output_device_id: Option<DeviceId>,
75) -> anyhow::Result<Box<dyn Any + Send>> {
76    let stop_signal = Arc::new(AtomicBool::new(false));
77
78    thread::Builder::new()
79        .name("AudioTestPlayback".to_string())
80        .spawn({
81            let stop_signal = stop_signal.clone();
82            move || {
83                let microphone = match open_test_microphone(input_device_id, stop_signal.clone()) {
84                    Ok(mic) => mic,
85                    Err(e) => {
86                        log::error!("Could not open microphone for audio test: {e}");
87                        return;
88                    }
89                };
90
91                let Ok(output) = audio::open_test_output(output_device_id) else {
92                    log::error!("Could not open output device for audio test");
93                    return;
94                };
95
96                // let microphone = rx.recv().unwrap();
97                output.mixer().add(microphone);
98
99                // Keep thread (and output device) alive until stop signal
100                while !stop_signal.load(Ordering::Relaxed) {
101                    thread::sleep(Duration::from_millis(100));
102                }
103            }
104        })?;
105
106    Ok(Box::new(util::defer(move || {
107        stop_signal.store(true, Ordering::Relaxed);
108    })))
109}
110
111fn open_test_microphone(
112    input_device_id: Option<DeviceId>,
113    stop_signal: Arc<AtomicBool>,
114) -> anyhow::Result<impl Source> {
115    let stream = audio::open_input_stream(input_device_id)?;
116    let stream = stream
117        .possibly_disconnected_channels_to_mono()
118        .constant_samplerate(SAMPLE_RATE)
119        .constant_params(CHANNEL_COUNT, SAMPLE_RATE)
120        .stoppable()
121        .periodic_access(
122            Duration::from_millis(50),
123            move |stoppable: &mut rodio::source::Stoppable<_>| {
124                if stop_signal.load(Ordering::Relaxed) {
125                    stoppable.stop();
126                }
127            },
128        );
129    Ok(stream)
130}
131
132impl Render for AudioTestWindow {
133    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
134        let is_testing = self._stop_playback.is_some();
135        let button_text = if is_testing {
136            "Stop Testing"
137        } else {
138            "Start Testing"
139        };
140
141        let button_style = if is_testing {
142            ButtonStyle::Tinted(ui::TintColor::Error)
143        } else {
144            ButtonStyle::Filled
145        };
146
147        let weak_entity = cx.entity().downgrade();
148        let input_dropdown = {
149            let weak_entity = weak_entity.clone();
150            render_audio_device_dropdown(
151                "audio-test-input-dropdown",
152                self.input_device_id.clone(),
153                true,
154                move |device_id, window, cx| {
155                    weak_entity
156                        .update(cx, |this, cx| {
157                            this.input_device_id = device_id.clone();
158                            cx.notify();
159                        })
160                        .log_err();
161                    let value: Option<AudioInputDeviceName> =
162                        device_id.map(|id| AudioInputDeviceName(Some(id.to_string())));
163                    update_settings_file(
164                        SettingsUiFile::User,
165                        Some("audio.experimental.input_audio_device"),
166                        window,
167                        cx,
168                        move |settings, _cx| {
169                            settings.audio.get_or_insert_default().input_audio_device = value;
170                        },
171                    )
172                    .log_err();
173                },
174                None,
175                None,
176                window,
177                cx,
178            )
179        };
180
181        let output_dropdown = render_audio_device_dropdown(
182            "audio-test-output-dropdown",
183            self.output_device_id.clone(),
184            false,
185            move |device_id, window, cx| {
186                weak_entity
187                    .update(cx, |this, cx| {
188                        this.output_device_id = device_id.clone();
189                        cx.notify();
190                    })
191                    .log_err();
192                let value: Option<AudioOutputDeviceName> =
193                    device_id.map(|id| AudioOutputDeviceName(Some(id.to_string())));
194                update_settings_file(
195                    SettingsUiFile::User,
196                    Some("audio.experimental.output_audio_device"),
197                    window,
198                    cx,
199                    move |settings, _cx| {
200                        settings.audio.get_or_insert_default().output_audio_device = value;
201                    },
202                )
203                .log_err();
204            },
205            None,
206            None,
207            window,
208            cx,
209        );
210
211        let content = v_flex()
212            .id("audio-test-window")
213            .track_focus(&self.focus_handle)
214            .size_full()
215            .p_4()
216            .when(cfg!(target_os = "macos"), |this| this.pt_10())
217            .gap_4()
218            .bg(cx.theme().colors().editor_background)
219            .child(
220                v_flex()
221                    .gap_1()
222                    .child(Label::new("Output Device"))
223                    .child(output_dropdown),
224            )
225            .child(
226                v_flex()
227                    .gap_1()
228                    .child(Label::new("Input Device"))
229                    .child(input_dropdown),
230            )
231            .child(
232                h_flex().w_full().justify_center().pt_4().child(
233                    Button::new("test-audio-toggle", button_text)
234                        .style(button_style)
235                        .on_click(cx.listener(|this, _, _, cx| this.toggle_testing(cx))),
236                ),
237            );
238
239        client_side_decorations(
240            v_flex()
241                .size_full()
242                .text_color(cx.theme().colors().text)
243                .children(self.title_bar.clone())
244                .child(content),
245            window,
246            cx,
247        )
248    }
249}
250
251impl Focusable for AudioTestWindow {
252    fn focus_handle(&self, _cx: &App) -> FocusHandle {
253        self.focus_handle.clone()
254    }
255}
256
257impl Drop for AudioTestWindow {
258    fn drop(&mut self) {
259        let _ = self._stop_playback.take();
260    }
261}
262
263pub fn open_audio_test_window(_window: &mut Window, cx: &mut App) {
264    let existing = cx
265        .windows()
266        .into_iter()
267        .find_map(|w| w.downcast::<AudioTestWindow>());
268
269    if let Some(existing) = existing {
270        existing
271            .update(cx, |_, window, _| window.activate_window())
272            .log_err();
273        return;
274    }
275
276    let app_id = ReleaseChannel::global(cx).app_id();
277    let window_size = Size {
278        width: px(640.0),
279        height: px(300.0),
280    };
281    let window_min_size = Size {
282        width: px(400.0),
283        height: px(240.0),
284    };
285
286    cx.open_window(
287        WindowOptions {
288            titlebar: Some(gpui::TitlebarOptions {
289                title: Some("Audio Test".into()),
290                appears_transparent: true,
291                traffic_light_position: Some(gpui::point(px(12.0), px(12.0))),
292            }),
293            focus: true,
294            show: true,
295            is_movable: true,
296            kind: WindowKind::Normal,
297            window_background: cx.theme().window_background_appearance(),
298            app_id: Some(app_id.to_owned()),
299            window_decorations: Some(gpui::WindowDecorations::Client),
300            window_bounds: Some(WindowBounds::centered(window_size, cx)),
301            window_min_size: Some(window_min_size),
302            ..Default::default()
303        },
304        |_, cx| cx.new(AudioTestWindow::new),
305    )
306    .log_err();
307}
308
Served at tenant.openagents/omega Member data and write actions are omitted.