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; 14 15// Roughly 53267 Hz 16const SAMPLE_RATE: f64 = NTSC_MCLK_FREQUENCY / 7.0 / 6.0 / 24.0; 17 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}