Skip to repository content308 lines · 9.9 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T03:42:58.059Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
audio_test_window.rs
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