1use egui::emath::OrderedFloat;
2use egui::scroll_area::ScrollBarVisibility;
3use egui::style::ScrollStyle;
4use egui::{Grid, ScrollArea, Ui, Window};
5use genesis_components::ym2612::{
6    Channel3FrequencyMode, ChannelRegisters, GlobalRegisters, OperatorRegisters, TimerState,
7    Ym2612DebugView,
8};
9use std::iter;
10
11const WINDOW_TITLE: &str = "YM2612 Registers";
12
13const NTSC_MCLK_FREQUENCY: f64 = 53_693_175.0;

Roughly 53267 Hz

16const SAMPLE_RATE: f64 = NTSC_MCLK_FREQUENCY / 7.0 / 6.0 / 24.0;
18const ALGORITHM_IMAGES: &[egui::ImageSource<'static>; 8] = &[
19    egui::include_image!("ym2612/algorithm0.png"),
20    egui::include_image!("ym2612/algorithm1.png"),
21    egui::include_image!("ym2612/algorithm2.png"),
22    egui::include_image!("ym2612/algorithm3.png"),
23    egui::include_image!("ym2612/algorithm4.png"),
24    egui::include_image!("ym2612/algorithm5.png"),
25    egui::include_image!("ym2612/algorithm6.png"),
26    egui::include_image!("ym2612/algorithm7.png"),
27];
28
29pub struct Ym2612DebugWindowState {
30    pub open: bool,
31    pub channel: u8,
32    pub ever_rendered: bool,
33}
34
35impl Ym2612DebugWindowState {
36    pub fn new() -> Self {
37        Self { open: false, channel: 0, ever_rendered: false }
38    }
39
40    pub fn open_window(&mut self, ctx: &egui::Context) {
41        self.open = true;
42        crate::move_to_top(ctx, WINDOW_TITLE);
43    }
44}
45
46pub fn render_debug_window(
47    ctx: &egui::Context,
48    ym2612: Ym2612DebugView<'_>,
49    state: &mut Ym2612DebugWindowState,
50) {
51    if state.open && !state.ever_rendered {
52        // Preload algorithm images to avoid flicker due to egui asynchronously loading images
53        for image in ALGORITHM_IMAGES {
54            if let egui::ImageSource::Bytes { uri, bytes } = image {
55                ctx.include_bytes(uri.clone(), bytes.clone());
56                let _ = ctx.try_load_image(uri, egui::SizeHint::Scale(OrderedFloat(1.0)));
57            }
58        }
59    }
60    state.ever_rendered |= state.open;
61
62    Window::new(WINDOW_TITLE)
63        .open(&mut state.open)
64        .constrain(false)
65        .resizable([true, true])
66        .default_pos([50.0, 50.0])
67        .default_size([700.0, 650.0])
68        .show(ctx, |ui| {
69            ui.horizontal(|ui| {
70                ui.label("Channel:");
71
72                for i in 0..6 {
73                    ui.radio_value(&mut state.channel, i, (i + 1).to_string());
74                }
75            });
76
77            ui.spacing_mut().scroll = ScrollStyle::solid();
78
79            ScrollArea::vertical()
80                .auto_shrink([true, false])
81                .scroll_bar_visibility(ScrollBarVisibility::AlwaysVisible)
82                .show(ui, |ui| {
83                    let global = ym2612.global_registers();
84                    let channel = ym2612.channel_registers();
85                    let operator = ym2612.operator_registers();
86
87                    ui.horizontal(|ui| {
88                        render_operator_registers(
89                            state.channel,
90                            &operator[state.channel as usize],
91                            &channel[state.channel as usize],
92                            &global,
93                            ui,
94                        );
95
96                        render_algorithm_image(channel[state.channel as usize].algorithm, ui);
97                    });
98
99                    ui.horizontal(|ui| {
100                        render_channel_registers(
101                            state.channel,
102                            &channel[state.channel as usize],
103                            ui,
104                        );
105                        render_global_registers(&global, ui);
106                    });
107
108                    render_timer_registers(&global, ui);
109                });
110        });
111}
112
113fn render_algorithm_image(algorithm: u8, ui: &mut Ui) {
114    ui.vertical_centered(|ui| {
115        ui.heading(format!("Algorithm {algorithm}"));
116
117        ui.add_space(10.0);
118
119        let image = ALGORITHM_IMAGES[algorithm as usize].clone();
120        ui.add(egui::Image::new(image).show_loading_spinner(false));
121    });
122}
123
124fn render_operator_registers(
125    channel_idx: u8,
126    operators: &[OperatorRegisters; 4],
127    channel: &ChannelRegisters,
128    global: &GlobalRegisters,
129    ui: &mut Ui,
130) {
131    ui.group(|ui| {
132        ui.vertical(|ui| {
133            ui.heading(format!("Channel {} Operator Registers", channel_idx + 1));
134
135            Grid::new("ym2612_operator_grid").min_col_width(70.0).show(ui, |ui| {
136                ui.label("");
137                ui.label("Operator 1");
138                ui.label("Operator 2");
139                ui.label("Operator 3");
140                ui.label("Operator 4");
141                ui.end_row();
142
143                ui.label("Keyed on");
144                for operator in operators {
145                    fake_checkbox(operator.key_on, ui);
146                }
147                ui.end_row();
148
149                let mut f_numbers = [channel.f_number; 4];
150                let mut blocks = [channel.block; 4];
151                if channel_idx == 2
152                    && matches!(
153                        global.channel_3_frequency_mode,
154                        Channel3FrequencyMode::PerOperator | Channel3FrequencyMode::Csm
155                    )
156                {
157                    f_numbers[..3].copy_from_slice(&global.channel_3_f_numbers);
158                    blocks[..3].copy_from_slice(&global.channel_3_blocks);
159                }
160
161                ui.label("F-number");
162                for f_number in f_numbers {
163                    ui.label(f_number.to_string());
164                }
165                ui.end_row();
166
167                ui.label("Block");
168                for block in blocks {
169                    ui.label(block.to_string());
170                }
171                ui.end_row();
172
173                ui.label("Frequency");
174                for (f_number, block) in iter::zip(f_numbers, blocks) {
175                    let frequency = channel_frequency(f_number, block);
176                    ui.label(format!("{frequency:.2} Hz"));
177                }
178                ui.end_row();
179
180                ui.label("Detune");
181                for operator in operators {
182                    let magnitude = operator.detune & 3;
183                    let sign = if operator.detune & 4 != 0 { "-" } else { "+" };
184                    ui.label(format!("{} ({sign}{magnitude})", operator.detune));
185                }
186                ui.end_row();
187
188                ui.label("Multiple");
189                for operator in operators {
190                    ui.label(operator.multiple.to_string());
191                }
192                ui.end_row();
193
194                ui.label("Attack rate");
195                for operator in operators {
196                    ui.label(hex_and_decimal(operator.attack_rate));
197                }
198                ui.end_row();
199
200                ui.label("Decay rate");
201                for operator in operators {
202                    ui.label(hex_and_decimal(operator.decay_rate));
203                }
204                ui.end_row();
205
206                ui.label("Sustain rate");
207                for operator in operators {
208                    ui.label(hex_and_decimal(operator.sustain_rate));
209                }
210                ui.end_row();
211
212                ui.label("Release rate");
213                for operator in operators {
214                    ui.label(format!(
215                        "0x{:02X} ({})",
216                        operator.release_rate,
217                        2 * operator.release_rate + 1
218                    ));
219                }
220                ui.end_row();
221
222                ui.label("Sustain level");
223                for operator in operators {
224                    ui.label(format!("0x{:02X}", operator.sustain_level));
225                }
226                ui.end_row();
227
228                ui.label("Total level");
229                for operator in operators {
230                    ui.label(format!("0x{:02X}", operator.total_level));
231                }
232                ui.end_row();
233
234                ui.label("Key scaling level");
235                for operator in operators {
236                    ui.label(operator.key_scale_level.to_string());
237                }
238                ui.end_row();
239
240                ui.label("LFO AM enabled");
241                for operator in operators {
242                    fake_checkbox(operator.tremolo_enabled, ui);
243                }
244                ui.end_row();
245
246                ui.label("SSG-EG enabled");
247                for operator in operators {
248                    fake_checkbox(operator.ssg_enabled, ui);
249                }
250                ui.end_row();
251
252                ui.label("SSG-EG attack");
253                for operator in operators {
254                    fake_checkbox(operator.ssg_attack, ui);
255                }
256                ui.end_row();
257
258                ui.label("SSG-EG alternate");
259                for operator in operators {
260                    fake_checkbox(operator.ssg_alternate, ui);
261                }
262                ui.end_row();
263
264                ui.label("SSG-EG hold");
265                for operator in operators {
266                    fake_checkbox(operator.ssg_hold, ui);
267                }
268                ui.end_row();
269            });
270        });
271    });
272}
273
274fn render_channel_registers(channel_idx: u8, channel: &ChannelRegisters, ui: &mut Ui) {
275    ui.group(|ui| {
276        ui.vertical(|ui| {
277            ui.heading(format!("Channel {} Registers", channel_idx + 1));
278
279            Grid::new("ym2612_channel_grid").min_col_width(70.0).show(ui, |ui| {
280                ui.label("Algorithm");
281                ui.label(channel.algorithm.to_string());
282                ui.end_row();
283
284                ui.label("Feedback level");
285                ui.label(channel.feedback_level.to_string());
286                ui.end_row();
287
288                ui.label("LFO FM sensitivity");
289                ui.label(channel.fm_sensitivity.to_string());
290                ui.end_row();
291
292                ui.label("LFO AM sensitivity");
293                ui.label(channel.am_sensitivity.to_string());
294                ui.end_row();
295
296                ui.label("L output");
297                fake_checkbox(channel.l_output, ui);
298                ui.end_row();
299
300                ui.label("R output");
301                fake_checkbox(channel.r_output, ui);
302                ui.end_row();
303            });
304        });
305    });
306}
307
308fn render_global_registers(global: &GlobalRegisters, ui: &mut Ui) {
309    ui.group(|ui| {
310        ui.vertical(|ui| {
311            ui.heading("Global Registers");
312
313            Grid::new("ym2612_global_grid").min_col_width(70.0).show(ui, |ui| {
314                ui.label("DAC channel enabled");
315                fake_checkbox(global.dac_channel_enabled, ui);
316                ui.end_row();
317
318                ui.label("DAC channel sample");
319                ui.label(global.dac_channel_sample.to_string());
320                ui.end_row();
321
322                ui.label("LFO enabled");
323                fake_checkbox(global.lfo.enabled, ui);
324                ui.end_row();
325
326                ui.label("LFO frequency");
327                ui.label(format!(
328                    "{} ({:.2} Hz)",
329                    global.lfo.frequency,
330                    lfo_frequency(global.lfo.divider)
331                ));
332                ui.end_row();
333
334                ui.label("Channel 3 frequency mode");
335                ui.label(
336                    if matches!(
337                        global.channel_3_frequency_mode,
338                        Channel3FrequencyMode::PerOperator | Channel3FrequencyMode::Csm
339                    ) {
340                        "Per-operator"
341                    } else {
342                        "Normal"
343                    },
344                );
345                ui.end_row();
346
347                ui.label("CSM enabled");
348                fake_checkbox(global.channel_3_frequency_mode == Channel3FrequencyMode::Csm, ui);
349                ui.end_row();
350            });
351        });
352    });
353}
354
355fn render_timer_registers(global: &GlobalRegisters, ui: &mut Ui) {
356    ui.horizontal(|ui| {
357        ui.group(|ui| {
358            ui.vertical(|ui| {
359                ui.heading("Timer A");
360
361                Grid::new("ym2612_timer_a_grid").show(ui, |ui| {
362                    render_timer_grid(&global.timer_a, timer_a_frequency, ui);
363                });
364            })
365        });
366
367        ui.group(|ui| {
368            ui.vertical(|ui| {
369                ui.heading("Timer B");
370
371                Grid::new("ym2612_timer_b_grid").show(ui, |ui| {
372                    render_timer_grid(&global.timer_b, timer_b_frequency, ui);
373                });
374            });
375        });
376    });
377}
378
379fn render_timer_grid(timer: &TimerState, frequency_fn: impl Fn(u16) -> f64, ui: &mut Ui) {
380    ui.label("Enabled");
381    fake_checkbox(timer.enabled, ui);
382    ui.end_row();
383
384    ui.label("Frequency");
385    ui.label(format!("{} ({:.2} Hz)", timer.interval, frequency_fn(timer.interval)));
386    ui.end_row();
387
388    ui.label("Overflow flag enabled");
389    fake_checkbox(timer.overflow_flag_enabled, ui);
390    ui.end_row();
391
392    ui.label("Overflow flag");
393    fake_checkbox(timer.overflow_flag, ui);
394    ui.end_row();
395}
396
397fn fake_checkbox(mut value: bool, ui: &mut Ui) {
398    ui.scope(|ui| {
399        ui.visuals_mut().disabled_alpha = 1.0;
400
401        ui.add_enabled_ui(false, |ui| ui.checkbox(&mut value, ""));
402    });
403}
404
405fn channel_frequency(f_number: u16, block: u8) -> f64 {
406    // fnote = Fnum * 2^(B-1) / 2^20 * sample_rate
407    SAMPLE_RATE * f64::from((f_number << block) >> 1) / f64::from(1 << 20)
408}
409
410fn timer_a_frequency(interval: u16) -> f64 {
411    SAMPLE_RATE / f64::from(1024 - interval)
412}
413
414fn timer_b_frequency(interval: u16) -> f64 {
415    SAMPLE_RATE / 16.0 / f64::from(256 - interval)
416}
417
418fn lfo_frequency(divider: u8) -> f64 {
419    SAMPLE_RATE / f64::from(divider) / 128.0
420}
421
422fn hex_and_decimal(value: u8) -> String {
423    format!("0x{value:02X} ({value})")
424}