1use cuneus::audio::PcmStreamManager;
2use cuneus::compute::*;
3use cuneus::prelude::*;
4use log::{error, info};
5
6const MAX_SAMPLES_PER_FRAME: u32 = 1024;
7const SAMPLE_RATE: u32 = 44100;
8
9cuneus::uniform_params! {
10 struct SongParams {
11 volume: f32,
12 tempo_multiplier: f32,
13 sample_offset: u32,
14 samples_to_generate: u32,
15 sample_rate: f32,
16 _pad1: f32,
17 _pad2: f32,
18 _pad3: f32,
19 }
20}
21
22struct VeridisQuo {
23 base: RenderKit,
24 compute_shader: ComputeShader,
25 current_params: SongParams,
26 pcm_stream: Option<PcmStreamManager>,
27 audio_start: std::time::Instant,
28 last_samples_generated: u32,
29}
30
31impl ShaderManager for VeridisQuo {
32 fn init(core: &Core) -> Self {
33 let base = RenderKit::new(core);
34
35 let initial_params = SongParams {
36 volume: 0.5,
37 tempo_multiplier: 1.0,
38 sample_offset: 0,
39 samples_to_generate: MAX_SAMPLES_PER_FRAME,
40 sample_rate: SAMPLE_RATE as f32,
41 _pad1: 0.0,
42 _pad2: 0.0,
43 _pad3: 0.0,
44 };
45
46 let audio_buffer_size = (MAX_SAMPLES_PER_FRAME * 2) as usize;
48
49 let config = ComputeShader::builder()
50 .with_entry_point("main")
51 .with_custom_uniforms::<SongParams>()
52 .with_fonts()
53 .with_audio(audio_buffer_size)
54 .with_workgroup_size([16, 16, 1])
55 .with_texture_format(COMPUTE_TEXTURE_FORMAT_RGBA16)
56 .with_label("Veridis Quo")
57 .build();
58
59 let compute_shader = cuneus::compute_shader!(core, "shaders/veridisquo.wgsl", config);
60 compute_shader.set_custom_params(initial_params, &core.queue);
61
62 let pcm_stream = match PcmStreamManager::new(Some(SAMPLE_RATE)) {
63 Ok(mut stream) => {
64 if let Err(e) = stream.start() {
65 error!("Failed to start PCM stream: {e}");
66 None
67 } else {
68 info!("PCM audio stream started at {SAMPLE_RATE} Hz");
69 Some(stream)
70 }
71 }
72 Err(e) => {
73 error!("Failed to create PCM stream: {e}");
74 None
75 }
76 };
77
78 Self {
79 base,
80 compute_shader,
81 current_params: initial_params,
82 pcm_stream,
83 audio_start: std::time::Instant::now(),
84 last_samples_generated: 0,
85 }
86 }
87
88 fn update(&mut self, core: &Core) {
89 let current_time = self.base.controls.get_time(&self.base.start_time);
90 let delta = 1.0 / 60.0;
91 self.compute_shader
92 .set_time(current_time, delta, &core.queue);
93
94 if let Some(ref mut stream) = self.pcm_stream {
95 let prev = self.last_samples_generated;
97 if prev > 0 {
98 if let Ok(audio_data) = pollster::block_on(
99 self.compute_shader
100 .read_audio_buffer(&core.device, &core.queue),
101 ) {
102 let count = (prev * 2) as usize;
103 if audio_data.len() >= count {
104 let _ = stream.push_samples(&audio_data[..count]);
105 }
106 }
107 }
108
109 let elapsed = self.audio_start.elapsed().as_secs_f64();
111 let target_samples = (elapsed * SAMPLE_RATE as f64) as u64;
112 let written = stream.samples_written();
113 let needed = (target_samples.saturating_sub(written) as u32).min(MAX_SAMPLES_PER_FRAME);
114 self.current_params.sample_offset = written as u32;
115 self.current_params.samples_to_generate = needed;
116 self.last_samples_generated = needed;
117 }
118 self.compute_shader
119 .set_custom_params(self.current_params, &core.queue);
120
121 self.compute_shader.handle_export(core, &mut self.base);
122 }
123
124 fn render(&mut self, core: &Core) -> Result<(), cuneus::SurfaceError> {
125 let mut frame = self.base.begin_frame(core)?;
126
127 let mut params = self.current_params;
128 let mut changed = false;
129 let mut controls_request = self
130 .base
131 .controls
132 .get_ui_request(&self.base.start_time, &core.size, self.base.fps_tracker.fps());
133
134 let full_output = if self.base.key_handler.show_ui {
135 self.base.render_ui(core, |ctx| {
136 RenderKit::apply_default_style(ctx);
137
138 egui::Window::new("Veridis Quo")
139 .collapsible(true)
140 .resizable(true)
141 .default_width(250.0)
142 .show(ctx, |ui| {
143 changed |= ui
144 .add(
145 egui::Slider::new(&mut params.volume, 0.0..=1.0).text("Volume"),
146 )
147 .changed();
148 changed |= ui
149 .add(
150 egui::Slider::new(&mut params.tempo_multiplier, 0.5..=2.0)
151 .text("Tempo"),
152 )
153 .changed();
154
155 if let Some(ref mut stream) = self.pcm_stream {
156 let mut vol = params.volume as f64;
157 if ui
158 .add(
159 egui::Slider::new(&mut vol, 0.0..=1.0)
160 .text("Master Volume"),
161 )
162 .changed()
163 {
164 stream.set_master_volume(vol);
165 }
166 }
167
168 ui.separator();
169 ShaderControls::render_controls_widget(ui, &mut controls_request);
170 });
171 })
172 } else {
173 self.base.render_ui(core, |_ctx| {})
174 };
175
176 if changed {
177 self.current_params = params;
178 }
179
180 self.base.apply_control_request(controls_request);
181
182 self.compute_shader.dispatch(&mut frame.encoder, core);
183
184 self.base.renderer.render_to_view(
185 &mut frame.encoder,
186 &frame.view,
187 &self.compute_shader.get_output_texture().bind_group,
188 );
189
190 self.base.end_frame(core, frame, full_output);
191
192 Ok(())
193 }
194
195 fn resize(&mut self, core: &Core) {
196 self.base.default_resize(core, &mut self.compute_shader);
197 }
198
199 fn handle_input(&mut self, core: &Core, event: &WindowEvent) -> bool {
200 if self
201 .base
202 .egui_state
203 .on_window_event(core.window(), event)
204 .consumed
205 {
206 return true;
207 }
208
209 if let WindowEvent::KeyboardInput { event, .. } = event {
210 if event.state == winit::event::ElementState::Pressed {
211 if let winit::keyboard::Key::Character(ref s) = event.logical_key {
212 if s.as_str() == "r" || s.as_str() == "R" {
213 self.base.start_time = std::time::Instant::now();
214 if let Some(ref mut stream) = self.pcm_stream {
216 let _ = stream.stop();
217 let _ = stream.start();
218 }
219 return true;
220 }
221 }
222 }
223 return self
224 .base
225 .key_handler
226 .handle_keyboard_input(core.window(), event);
227 }
228
229 false
230 }
231}
232
233fn main() -> Result<(), Box<dyn std::error::Error>> {
234 env_logger::init();
235 cuneus::gst::init()?;
236
237 let (app, event_loop) = ShaderApp::new("Veridis Quo", 800, 600);
238
239 app.run(event_loop, VeridisQuo::init)
240}