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