soundview/
events.rs

1use anyhow::{anyhow, Context, Result};
2use crossbeam_channel::Receiver;
3use sdl2::event::{Event, WindowEvent};
4use sdl2::keyboard::Keycode;
5use tracing::{debug, error, warn};
6
7use crate::webgpu;
8use crate::{hsl, recorder, renderer, webgpu::Orientation};
9
10/// Endless loop that takes over the main thread.
11/// Drives rendering the visualization and handles any keyboard/window events.
12pub fn process_event_loop(
13    sdl_context: &sdl2::Sdl,
14    recv_processed: Receiver<Vec<f32>>,
15    orientation: Orientation,
16    fullscreen: bool,
17    scroll_rate: f32,
18    texture_width: usize,
19    mut rec: recorder::Recorder,
20    fourier_thread: std::thread::JoinHandle<()>,
21) -> Result<()> {
22    let icon_webp = include_bytes!("soundview.webp");
23    let icon_image = image::load_from_memory_with_format(icon_webp, image::ImageFormat::WebP)?;
24    let mut icon_bytes = icon_image
25        .as_rgba8()
26        .context("Unable to get RGBA data for icon")?
27        .to_vec();
28    let icon = sdl2::surface::Surface::from_data(
29        &mut icon_bytes,
30        icon_image.width(),
31        icon_image.height(),
32        sdl2::pixels::PixelFormatEnum::RGBA32.byte_size_of_pixels(icon_image.width() as usize)
33            as u32,
34        sdl2::pixels::PixelFormatEnum::RGBA32,
35    )
36    .map_err(|e| anyhow!(e))
37    .context("Failed to init application icon")?;
38
39    let mut window_builder =
40        sdl_context
41            .video()
42            .map_err(|e| anyhow!(e))?
43            .window("soundview", 1280, 720);
44    window_builder.position_centered().resizable().metal_view();
45    if fullscreen {
46        window_builder.fullscreen_desktop();
47        sdl_context.mouse().show_cursor(false);
48    }
49    let mut window = window_builder.build().map_err(|e| anyhow!(e))?;
50    window.set_minimum_size(100, 100).map_err(|e| anyhow!(e))?;
51    window.set_icon(icon);
52
53    let fourier_thread_rc = std::cell::RefCell::new(Some(fourier_thread));
54    let wgpu_state = pollster::block_on(webgpu::WgpuState::new(&window))?;
55    let hsl = hsl::HSL::new(wgpu_state.preferred_format, 50, 40);
56
57    let mut state = pollster::block_on(renderer::State::new(
58        wgpu_state,
59        hsl,
60        recv_processed,
61        orientation,
62        scroll_rate,
63        texture_width,
64    ));
65
66    let mut event_pump = sdl_context.event_pump().map_err(|e| anyhow!(e))?;
67    loop {
68        // Handle any pending events
69        for event in event_pump.poll_iter() {
70            match event {
71                // Close window button: Quit
72                Event::Quit { .. } => {
73                    shut_down(&mut rec, &fourier_thread_rc);
74                    return Ok(());
75                }
76
77                Event::Window {
78                    window_id,
79                    win_event: WindowEvent::SizeChanged(width, height),
80                    ..
81                } if window_id == window.id() => {
82                    state.resize(Some(webgpu::Dimensions {
83                        width: width as usize,
84                        height: height as usize,
85                    }));
86                }
87
88                Event::KeyDown {
89                    keycode: Some(keycode),
90                    ..
91                } => match keycode {
92                    // Esc or Q: Quit
93                    Keycode::Escape | Keycode::Q => {
94                        shut_down(&mut rec, &fourier_thread_rc);
95                        return Ok(());
96                    }
97                    // F11 or F: Fullscreen
98                    Keycode::F11 | Keycode::F => {
99                        if window.fullscreen_state() == sdl2::video::FullscreenType::Off {
100                            // toggle on
101                            if let Err(e) =
102                                window.set_fullscreen(sdl2::video::FullscreenType::Desktop)
103                            {
104                                warn!("Failed to enable fullscreen: {}", e);
105                            }
106                            sdl_context.mouse().show_cursor(false);
107                        } else {
108                            // toggle off
109                            if let Err(e) = window.set_fullscreen(sdl2::video::FullscreenType::Off)
110                            {
111                                warn!("Failed to disable fullscreen: {}", e);
112                            }
113                            sdl_context.mouse().show_cursor(true);
114                        }
115                    }
116                    // Space or R: Rotate
117                    Keycode::Space | Keycode::R => {
118                        state.toggle_orientation();
119                    }
120                    // Left arrow: Prev device
121                    Keycode::Left => {
122                        // TODO display device name on top corner of display, using font-rs or manual bitmap
123                        if let Err(e) = rec.prev_device() {
124                            warn!("Failed to switch to previous device: {}", e)
125                        }
126                    }
127                    // Right arrow: Next device
128                    Keycode::Right => {
129                        // TODO display device name on top corner of display, using font-rs or manual bitmap
130                        if let Err(e) = rec.next_device() {
131                            warn!("Failed to switch to next device: {}", e)
132                        }
133                    }
134                    // other keys...
135                    _ => {}
136                },
137
138                // other events...
139                _ => {}
140            }
141        }
142
143        // Render
144        match state.surface_texture() {
145            Ok(output) => {
146                if let Err(e) = state.render(output) {
147                    error!("shutting down: render error {}", e);
148                    shut_down(&mut rec, &fourier_thread_rc);
149                    return Ok(());
150                }
151            }
152            // Reconfigure the surface if lost
153            Err(wgpu::SurfaceError::Lost) => state.resize(None),
154            // The system is out of memory, we should probably quit
155            Err(wgpu::SurfaceError::OutOfMemory) => {
156                // Run any teardown operations before exiting the main thread.
157                // event_loop.run() has taken over the thread and will never return.
158                error!("shutting down: wgpu is out of memory");
159                rec.stop();
160                match fourier_thread_rc.replace(None) {
161                    Some(fourier_thread) => {
162                        if let Err(e) = fourier_thread.join() {
163                            warn!("failed to wait on fourier thread to exit: {:?}", e);
164                        }
165                    }
166                    None => {} // join already called?
167                }
168                return Ok(());
169            }
170            // All other errors (Outdated, Timeout) should be resolved by rendering the next frame.
171            // Timeout errors in particular can happen every ~1s when the window is being obscured.
172            Err(e) => debug!("render error: {:?}", e),
173        }
174    }
175}
176
177/// Runs any teardown operations before exiting the main thread.
178/// event_loop.run() has taken over the thread and will never return.
179fn shut_down(
180    rec: &mut recorder::Recorder,
181    fourier_thread_rc: &std::cell::RefCell<Option<std::thread::JoinHandle<()>>>,
182) {
183    debug!("shutting down");
184    rec.stop();
185    match fourier_thread_rc.replace(None) {
186        Some(fourier_thread) => {
187            if let Err(e) = fourier_thread.join() {
188                warn!("failed to wait on fourier thread to exit: {:?}", e);
189            }
190        }
191        None => {} // join already called?
192    }
193}