Skip to main content

veridisquo/
veridisquo.rs

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        // Audio buffer: interleaved stereo f32 → need 2x samples
47        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            // Push previous frame's audio
96            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            // Calculate this frame's needs
110            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                        // Reset audio stream
215                        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}