| 9120e874 Andy McCall |
1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
|
| 12 |
|
| 13 |
#include "imgui.h" |
| 14 |
#include "debug_ui_panels.h" |
| 15 |
#include "debug_ui_bridge.h" |
| 16 |
#include "debug_ui_scope.h" |
| 17 |
#include "debug_ui_widgets.h" |
| 18 |
|
| 19 |
#include <math.h> |
| 20 |
#include <stdint.h> |
| 21 |
#include <stdio.h> |
| 22 |
|
| 23 |
namespace { |
| 24 |
|
| 25 |
constexpr uint32_t PSG_BASE_ADDR = 0x1F9C0; |
| 26 |
constexpr double PSG_SAMPLE_RATE = 25000000.0 / 512.0; |
| 27 |
constexpr double PSG_PHASE_STEPS = 131072.0; |
| 28 |
constexpr float PSG_PEAK_FULLSCALE = 16352.0f; |
| 29 |
constexpr float LEVEL_METER_W = 92.0f; |
| 30 |
|
| 31 |
struct PsgSnapshot { |
| 32 |
psg_debug_channel ch[PSG_NUM_CHANNELS]; |
| 33 |
int audible = 0; |
| 34 |
}; |
| 35 |
|
| 36 |
const char * |
| 37 |
waveform_name(uint8_t wf) |
| 38 |
{ |
| 39 |
static const char *names[] = { "Pulse", "Saw", "Tri", "Noise" }; |
| 40 |
return names[wf & 3]; |
| 41 |
} |
| 42 |
|
| 43 |
ImU32 |
| 44 |
waveform_color(uint8_t wf, bool silent) |
| 45 |
{ |
| 46 |
if (silent) |
| 47 |
return IM_COL32(95, 100, 108, 255); |
| 48 |
switch (wf & 3) { |
| 49 |
case PSG_WF_PULSE: return IM_COL32(100, 190, 255, 255); |
| 50 |
case PSG_WF_SAWTOOTH: return IM_COL32(255, 185, 90, 255); |
| 51 |
case PSG_WF_TRIANGLE: return IM_COL32(130, 225, 145, 255); |
| 52 |
default: return IM_COL32(220, 130, 255, 255); |
| 53 |
} |
| 54 |
} |
| 55 |
|
| 56 |
bool |
| 57 |
voice_silent(const psg_debug_channel &c) |
| 58 |
{ |
| 59 |
return c.vol_raw == 0 || (!c.left && !c.right); |
| 60 |
} |
| 61 |
|
| 62 |
double |
| 63 |
freq_hz(uint16_t freq) |
| 64 |
{ |
| 65 |
return (double)freq * PSG_SAMPLE_RATE / PSG_PHASE_STEPS; |
| 66 |
} |
| 67 |
|
| 68 |
void |
| 69 |
format_note(double hz, char *out, size_t outsz) |
| 70 |
{ |
| 71 |
if (hz <= 0.0) { |
| 72 |
snprintf(out, outsz, "--"); |
| 73 |
return; |
| 74 |
} |
| 75 |
|
| 76 |
static const char *names[] = { |
| 77 |
"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" |
| 78 |
}; |
| 79 |
const double semis = 69.0 + 12.0 * log(hz / 440.0) / log(2.0); |
| 80 |
const int note = (int)floor(semis + 0.5); |
| 81 |
const int idx = ((note % 12) + 12) % 12; |
| 82 |
const int oct = note / 12 - 1; |
| 83 |
int cents = (int)floor((semis - (double)note) * 100.0 + ((semis >= (double)note) ? 0.5 : -0.5)); |
| 84 |
|
| 85 |
char nb[12]; |
| 86 |
snprintf(nb, sizeof nb, "%s%d", names[idx], oct); |
| 87 |
snprintf(out, outsz, "%-4s %+3d c", nb, cents); |
| 88 |
} |
| 89 |
|
| 90 |
void |
| 91 |
read_channels(PsgSnapshot *snap) |
| 92 |
{ |
| 93 |
snap->audible = 0; |
| 94 |
for (int ch = 0; ch < PSG_NUM_CHANNELS; ++ch) { |
| 95 |
if (!psg_debug_get_channel(ch, &snap->ch[ch])) { |
| 96 |
snap->ch[ch] = {}; |
| 97 |
} |
| 98 |
if (!voice_silent(snap->ch[ch])) { |
| 99 |
++snap->audible; |
| 100 |
} |
| 101 |
} |
| 102 |
} |
| 103 |
|
| 104 |
|
| 105 |
|
| 106 |
|
| 107 |
|
| 108 |
|
| 109 |
const PsgSnapshot & |
| 110 |
current_channels() |
| 111 |
{ |
| 112 |
static PsgSnapshot held; |
| 113 |
static bool primed = false; |
| 114 |
if (!primed || !dbgui_audio_hold()) { |
| 115 |
read_channels(&held); |
| 116 |
primed = true; |
| 117 |
} |
| 118 |
return held; |
| 119 |
} |
| 120 |
|
| 121 |
int |
| 122 |
audible_from_regs() |
| 123 |
{ |
| 124 |
int audible = 0; |
| 125 |
for (int ch = 0; ch < PSG_NUM_CHANNELS; ++ch) { |
| 126 |
uint8_t v = psg_debug_get_reg((uint8_t)(ch * 4 + 2)); |
| 127 |
if ((v & 0x3F) != 0 && (v & 0xC0) != 0) { |
| 128 |
++audible; |
| 129 |
} |
| 130 |
} |
| 131 |
return audible; |
| 132 |
} |
| 133 |
|
| 134 |
void |
| 135 |
show_status_line() |
| 136 |
{ |
| 137 |
ImGui::Text("Sample %.3f Hz audible voices %2d/%2d noise LFSR $%04X", |
| 138 |
PSG_SAMPLE_RATE, audible_from_regs(), PSG_NUM_CHANNELS, |
| 139 |
psg_debug_get_noise_state()); |
| 140 |
} |
| 141 |
|
| 142 |
void |
| 143 |
text_value(const char *desc, const char *fmt, uint32_t v, int bytes) |
| 144 |
{ |
| 145 |
ImGui::Text(fmt, v); |
| 146 |
dbgui_hover_value_tooltip(desc, v, bytes); |
| 147 |
} |
| 148 |
|
| 149 |
|
| 150 |
|
| 151 |
|
| 152 |
|
| 153 |
|
| 154 |
|
| 155 |
|
| 156 |
|
| 157 |
|
| 158 |
float |
| 159 |
col_w(const char *sample) |
| 160 |
{ |
| 161 |
return ImGui::CalcTextSize(sample).x + ImGui::GetStyle().CellPadding.x * 2.0f; |
| 162 |
} |
| 163 |
|
| 164 |
void |
| 165 |
format_pw(const psg_debug_channel &c, char *out, size_t outsz) |
| 166 |
{ |
| 167 |
|
| 168 |
|
| 169 |
switch (c.waveform & 3) { |
| 170 |
case PSG_WF_PULSE: |
| 171 |
snprintf(out, outsz, "%2u (%5.2f%%)", c.pw, (double)(c.pw + 1) * 100.0 / 128.0); |
| 172 |
break; |
| 173 |
case PSG_WF_SAWTOOTH: |
| 174 |
case PSG_WF_TRIANGLE: |
| 175 |
snprintf(out, outsz, "mask $%02X", (unsigned)((c.pw ^ 0x3F) & 0x3F)); |
| 176 |
break; |
| 177 |
default: |
| 178 |
snprintf(out, outsz, "unused"); |
| 179 |
break; |
| 180 |
} |
| 181 |
} |
| 182 |
|
| 183 |
const char * |
| 184 |
reg_summary(int byte, uint8_t v) |
| 185 |
{ |
| 186 |
|
| 187 |
|
| 188 |
static char buf[96]; |
| 189 |
switch (byte & 3) { |
| 190 |
case 0: |
| 191 |
snprintf(buf, sizeof buf, "frequency low byte"); |
| 192 |
break; |
| 193 |
case 1: |
| 194 |
snprintf(buf, sizeof buf, "frequency high byte"); |
| 195 |
break; |
| 196 |
case 2: |
| 197 |
snprintf(buf, sizeof buf, "R=%-3s L=%-3s volume=%2u", |
| 198 |
(v & 0x80) ? "on" : "off", (v & 0x40) ? "on" : "off", |
| 199 |
(unsigned)(v & 0x3F)); |
| 200 |
break; |
| 201 |
default: |
| 202 |
snprintf(buf, sizeof buf, "wave=%-5s PW/shape=%2u", |
| 203 |
waveform_name((uint8_t)(v >> 6)), (unsigned)(v & 0x3F)); |
| 204 |
break; |
| 205 |
} |
| 206 |
return buf; |
| 207 |
} |
| 208 |
|
| 209 |
void |
| 210 |
draw_voice_detail(int selected, const psg_debug_channel &c) |
| 211 |
{ |
| 212 |
uint8_t regs[4]; |
| 213 |
for (int i = 0; i < 4; ++i) { |
| 214 |
regs[i] = psg_debug_get_reg((uint8_t)(selected * 4 + i)); |
| 215 |
} |
| 216 |
|
| 217 |
char note[32]; |
| 218 |
char pw[48]; |
| 219 |
const double hz = freq_hz(c.freq); |
| 220 |
format_note(hz, note, sizeof note); |
| 221 |
format_pw(c, pw, sizeof pw); |
| 222 |
|
| 223 |
ImGui::Separator(); |
| 224 |
ImGui::Text("Voice %d detail", selected); |
| 225 |
|
| 226 |
|
| 227 |
|
| 228 |
|
| 229 |
|
| 230 |
const float detail_h = ImGui::GetTextLineHeightWithSpacing() * 14.0f; |
| 231 |
if (!ImGui::BeginChild("psg_voice_detail", ImVec2(0.0f, detail_h), ImGuiChildFlags_None)) { |
| 232 |
ImGui::EndChild(); |
| 233 |
return; |
| 234 |
} |
| 235 |
|
| 236 |
if (!c.left && !c.right) { |
| 237 |
ImGui::TextColored(ImVec4(0.75f, 0.75f, 0.75f, 1.0f), |
| 238 |
"L and R are both disabled: the core holds phase at 0 (normal off state)."); |
| 239 |
} else if (c.vol_raw == 0) { |
| 240 |
ImGui::TextDisabled("Volume is zero: phase runs but output is silent."); |
| 241 |
} else { |
| 242 |
ImGui::TextDisabled("Voice is audible."); |
| 243 |
} |
| 244 |
|
| 245 |
if (ImGui::BeginTable("psg_detail_regs", 4, ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_RowBg | |
| 246 |
ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Resizable)) { |
| 247 |
ImGui::TableSetupColumn("Addr", ImGuiTableColumnFlags_WidthFixed, col_w("$1F9FF_")); |
| 248 |
ImGui::TableSetupColumn("Hex", ImGuiTableColumnFlags_WidthFixed, col_w("$FF_")); |
| 249 |
ImGui::TableSetupColumn("Bits", ImGuiTableColumnFlags_WidthFixed, col_w("1111 1111_")); |
| 250 |
ImGui::TableSetupColumn("Decode", ImGuiTableColumnFlags_WidthFixed, col_w("R=off L=off volume=63__")); |
| 251 |
ImGui::TableHeadersRow(); |
| 252 |
for (int i = 0; i < 4; ++i) { |
| 253 |
char bits[16]; |
| 254 |
dbgui_format_binary(bits, sizeof bits, regs[i], 8); |
| 255 |
ImGui::TableNextRow(); |
| 256 |
ImGui::TableSetColumnIndex(0); |
| 257 |
text_value("VRAM mirror address", "$%05X", PSG_BASE_ADDR + (uint32_t)selected * 4u + (uint32_t)i, 4); |
| 258 |
ImGui::TableSetColumnIndex(1); |
| 259 |
text_value("raw PSG shadow byte", "$%02X", regs[i], 1); |
| 260 |
ImGui::TableSetColumnIndex(2); |
| 261 |
ImGui::TextUnformatted(bits); |
| 262 |
ImGui::TableSetColumnIndex(3); |
| 263 |
ImGui::TextUnformatted(reg_summary(i, regs[i])); |
| 264 |
} |
| 265 |
ImGui::EndTable(); |
| 266 |
} |
| 267 |
|
| 268 |
ImGui::Text("Frequency: $%04X / %5u = %10.6f Hz", c.freq, c.freq, hz); |
| 269 |
dbgui_hover_value_tooltip("16-bit frequency word", c.freq, 2); |
| 270 |
ImGui::Text("Nearest note: %s", note); |
| 271 |
|
| 272 |
if ((c.waveform & 3) == PSG_WF_PULSE) { |
| 273 |
ImGui::Text("PW: %s duty; core compares (phase >> 10) > PW.", pw); |
| 274 |
ImGui::TextDisabled("Duty cycle only applies to the Pulse waveform."); |
| 275 |
} else if ((c.waveform & 3) == PSG_WF_SAWTOOTH || (c.waveform & 3) == PSG_WF_TRIANGLE) { |
| 276 |
ImGui::Text("PW: %2u, shape mask $%02X", c.pw, (unsigned)((c.pw ^ 0x3F) & 0x3F)); |
| 277 |
ImGui::TextDisabled("For Saw/Triangle this is an XOR wave-shaping mask, not duty cycle."); |
| 278 |
} else { |
| 279 |
ImGui::Text("PW: unused by Noise voices."); |
| 280 |
ImGui::TextDisabled("Noise latches a 6-bit value from the shared LFSR each phase wrap."); |
| 281 |
} |
| 282 |
|
| 283 |
ImGui::Text("Volume: raw %2u -> LUT %3u", (unsigned)c.vol_raw, (unsigned)c.vol_lut); |
| 284 |
ImGui::Text("Pan: L %-3s R %-3s", c.left ? "on" : "off", c.right ? "on" : "off"); |
| 285 |
ImGui::Text("Live phase: $%05X", (unsigned)(c.phase & 0x1FFFFu)); |
| 286 |
dbgui_hover_value_tooltip("17-bit phase accumulator", c.phase & 0x1FFFFu, 4); |
| 287 |
ImGui::Text("Last sample: %6d peak: %5u", (int)c.last_sample, (unsigned)c.peak); |
| 288 |
if ((c.waveform & 3) == PSG_WF_NOISE) { |
| 289 |
ImGui::Text("Latched noise value: %2u shared LFSR: $%04X", |
| 290 |
(unsigned)c.noiseval, (unsigned)psg_debug_get_noise_state()); |
| 291 |
} |
| 292 |
ImGui::EndChild(); |
| 293 |
} |
| 294 |
|
| 295 |
void |
| 296 |
draw_voices_tab(int *selected_voice) |
| 297 |
{ |
| 298 |
PsgSnapshot snap = current_channels(); |
| 299 |
if (dbgui_audio_hold()) { |
| 300 |
ImGui::TextColored(ImVec4(0.95f, 0.70f, 0.30f, 1.0f), "held (paused)"); |
| 301 |
ImGui::SetItemTooltip("The machine is paused, so audio is silent and the peak meters\n" |
| 302 |
"are no longer being fed. These are the values from the moment\n" |
| 303 |
"it stopped."); |
| 304 |
} |
| 305 |
|
| 306 |
const ImGuiTableFlags flags = ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersInnerV | |
| 307 |
ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollY | |
| 308 |
ImGuiTableFlags_ScrollX | ImGuiTableFlags_Resizable; |
| 309 |
if (ImGui::BeginTable("psg_voices", 10, flags, ImVec2(0, 310))) { |
| 310 |
ImGui::TableSetupScrollFreeze(0, 1); |
| 311 |
|
| 312 |
|
| 313 |
ImGui::TableSetupColumn("Ch", ImGuiTableColumnFlags_WidthFixed, col_w("Ch__")); |
| 314 |
ImGui::TableSetupColumn("Wave", ImGuiTableColumnFlags_WidthFixed, col_w("Noise_")); |
| 315 |
ImGui::TableSetupColumn("Freq", ImGuiTableColumnFlags_WidthFixed, col_w("$FFFF / 65535_")); |
| 316 |
ImGui::TableSetupColumn("Hz", ImGuiTableColumnFlags_WidthFixed, col_w("24414.062_")); |
| 317 |
ImGui::TableSetupColumn("Note", ImGuiTableColumnFlags_WidthFixed, col_w("A#10 -50 c_")); |
| 318 |
ImGui::TableSetupColumn("PW", ImGuiTableColumnFlags_WidthFixed, col_w("63 (50.00%)_")); |
| 319 |
ImGui::TableSetupColumn("Vol", ImGuiTableColumnFlags_WidthFixed, col_w("63 -> 511_")); |
| 320 |
ImGui::TableSetupColumn("L", ImGuiTableColumnFlags_WidthFixed, col_w("on_")); |
| 321 |
ImGui::TableSetupColumn("R", ImGuiTableColumnFlags_WidthFixed, col_w("on_")); |
| 322 |
ImGui::TableSetupColumn("Level", ImGuiTableColumnFlags_WidthFixed, LEVEL_METER_W); |
| 323 |
ImGui::TableHeadersRow(); |
| 324 |
|
| 325 |
for (int ch = 0; ch < PSG_NUM_CHANNELS; ++ch) { |
| 326 |
const psg_debug_channel &c = snap.ch[ch]; |
| 327 |
const bool silent = voice_silent(c); |
| 328 |
char note[32], pw[48], label[16]; |
| 329 |
format_note(freq_hz(c.freq), note, sizeof note); |
| 330 |
format_pw(c, pw, sizeof pw); |
| 331 |
snprintf(label, sizeof label, "%2d", ch); |
| 332 |
|
| 333 |
ImGui::TableNextRow(); |
| 334 |
if (silent) { |
| 335 |
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.48f); |
| 336 |
} |
| 337 |
|
| 338 |
ImGui::TableSetColumnIndex(0); |
| 339 |
if (ImGui::Selectable(label, *selected_voice == ch, ImGuiSelectableFlags_SpanAllColumns)) { |
| 340 |
*selected_voice = ch; |
| 341 |
} |
| 342 |
ImGui::TableSetColumnIndex(1); |
| 343 |
ImGui::TextUnformatted(waveform_name(c.waveform)); |
| 344 |
ImGui::TableSetColumnIndex(2); |
| 345 |
ImGui::Text("$%04X / %5u", (unsigned)c.freq, (unsigned)c.freq); |
| 346 |
dbgui_hover_value_tooltip("16-bit frequency word", c.freq, 2); |
| 347 |
ImGui::TableSetColumnIndex(3); |
| 348 |
ImGui::Text("%9.3f", freq_hz(c.freq)); |
| 349 |
ImGui::TableSetColumnIndex(4); |
| 350 |
ImGui::TextUnformatted(note); |
| 351 |
ImGui::TableSetColumnIndex(5); |
| 352 |
ImGui::TextUnformatted(pw); |
| 353 |
ImGui::TableSetColumnIndex(6); |
| 354 |
ImGui::Text("%2u -> %3u", (unsigned)c.vol_raw, (unsigned)c.vol_lut); |
| 355 |
dbgui_hover_value_tooltip("volume register value", c.vol_raw, 1); |
| 356 |
ImGui::TableSetColumnIndex(7); |
| 357 |
ImGui::TextUnformatted(c.left ? "on" : "-"); |
| 358 |
ImGui::TableSetColumnIndex(8); |
| 359 |
ImGui::TextUnformatted(c.right ? "on" : "-"); |
| 360 |
ImGui::TableSetColumnIndex(9); |
| 361 |
char meter_id[16]; |
| 362 |
snprintf(meter_id, sizeof meter_id, "##lvl%d", ch); |
| 363 |
dbgui_level_meter(meter_id, (float)c.peak / PSG_PEAK_FULLSCALE, |
| 364 |
ImVec2(LEVEL_METER_W, ImGui::GetTextLineHeight()), waveform_color(c.waveform, silent)); |
| 365 |
|
| 366 |
if (silent) { |
| 367 |
ImGui::PopStyleVar(); |
| 368 |
} |
| 369 |
} |
| 370 |
ImGui::EndTable(); |
| 371 |
} |
| 372 |
|
| 373 |
if (*selected_voice < 0 || *selected_voice >= PSG_NUM_CHANNELS) { |
| 374 |
*selected_voice = 0; |
| 375 |
} |
| 376 |
draw_voice_detail(*selected_voice, snap.ch[*selected_voice]); |
| 377 |
} |
| 378 |
|
| 379 |
void |
| 380 |
draw_registers_tab() |
| 381 |
{ |
| 382 |
ImGui::TextDisabled("VERA mirrors these at $1F9C0-$1F9FF; this panel reads the PSG shadow, which psg_reset() clears."); |
| 383 |
|
| 384 |
const ImGuiTableFlags flags = ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersInnerV | |
| 385 |
ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollY | |
| 386 |
ImGuiTableFlags_ScrollX | ImGuiTableFlags_Resizable; |
| 387 |
if (ImGui::BeginTable("psg_regs", 5, flags, ImVec2(0, 0))) { |
| 388 |
ImGui::TableSetupScrollFreeze(0, 1); |
| 389 |
ImGui::TableSetupColumn("Voice", ImGuiTableColumnFlags_WidthFixed, col_w("15 +3_")); |
| 390 |
ImGui::TableSetupColumn("Addr", ImGuiTableColumnFlags_WidthFixed, col_w("$1F9FF_")); |
| 391 |
ImGui::TableSetupColumn("Hex", ImGuiTableColumnFlags_WidthFixed, col_w("$FF_")); |
| 392 |
ImGui::TableSetupColumn("Bits", ImGuiTableColumnFlags_WidthFixed, col_w("1111 1111_")); |
| 393 |
ImGui::TableSetupColumn("Decode", ImGuiTableColumnFlags_WidthFixed, col_w("R=off L=off volume=63__")); |
| 394 |
ImGui::TableHeadersRow(); |
| 395 |
|
| 396 |
for (int ch = 0; ch < PSG_NUM_CHANNELS; ++ch) { |
| 397 |
for (int i = 0; i < 4; ++i) { |
| 398 |
const int reg = ch * 4 + i; |
| 399 |
const uint8_t v = psg_debug_get_reg((uint8_t)reg); |
| 400 |
char bits[16]; |
| 401 |
dbgui_format_binary(bits, sizeof bits, v, 8); |
| 402 |
|
| 403 |
ImGui::TableNextRow(); |
| 404 |
ImGui::TableSetColumnIndex(0); |
| 405 |
ImGui::Text("%2d +%d", ch, i); |
| 406 |
ImGui::TableSetColumnIndex(1); |
| 407 |
text_value("VRAM mirror address", "$%05X", PSG_BASE_ADDR + (uint32_t)reg, 4); |
| 408 |
ImGui::TableSetColumnIndex(2); |
| 409 |
text_value("raw PSG shadow byte", "$%02X", v, 1); |
| 410 |
ImGui::TableSetColumnIndex(3); |
| 411 |
ImGui::TextUnformatted(bits); |
| 412 |
ImGui::TableSetColumnIndex(4); |
| 413 |
ImGui::TextUnformatted(reg_summary(i, v)); |
| 414 |
} |
| 415 |
} |
| 416 |
ImGui::EndTable(); |
| 417 |
} |
| 418 |
} |
| 419 |
|
| 420 |
void |
| 421 |
read_scope_buffers(int16_t buffers[PSG_SCOPE_STREAMS][PSG_SCOPE_SAMPLES], unsigned counts[PSG_SCOPE_STREAMS]) |
| 422 |
{ |
| 423 |
for (int i = 0; i < PSG_SCOPE_STREAMS; ++i) { |
| 424 |
counts[i] = psg_debug_scope_read(i, buffers[i], PSG_SCOPE_SAMPLES); |
| 425 |
} |
| 426 |
} |
| 427 |
|
| 428 |
void |
| 429 |
draw_scope_tab(int *selected_voice) |
| 430 |
{ |
| 431 |
static DbgScopeControls ctl; |
| 432 |
static int16_t buffers[PSG_SCOPE_STREAMS][PSG_SCOPE_SAMPLES]; |
| 433 |
static unsigned counts[PSG_SCOPE_STREAMS]; |
| 434 |
static bool captured = false; |
| 435 |
|
| 436 |
PsgSnapshot snap = current_channels(); |
| 437 |
|
| 438 |
dbgui_scope_controls(&ctl, PSG_SCOPE_SAMPLES); |
| 439 |
|
| 440 |
|
| 441 |
const bool held = dbgui_audio_hold(); |
| 442 |
bool predicted = false; |
| 443 |
if (!ctl.freeze && !held) { |
| 444 |
read_scope_buffers(buffers, counts); |
| 445 |
captured = false; |
| 446 |
for (int i = 0; i < PSG_SCOPE_STREAMS; ++i) { |
| 447 |
if (counts[i] > 0) { |
| 448 |
captured = true; |
| 449 |
break; |
| 450 |
} |
| 451 |
} |
| 452 |
} else if (held && !captured && !ctl.freeze) { |
| 453 |
|
| 454 |
|
| 455 |
|
| 456 |
|
| 457 |
const unsigned n = psg_debug_scope_predict(buffers, PSG_SCOPE_SAMPLES); |
| 458 |
for (int i = 0; i < PSG_SCOPE_STREAMS; ++i) { |
| 459 |
counts[i] = n; |
| 460 |
} |
| 461 |
predicted = true; |
| 462 |
} |
| 463 |
if (held) { |
| 464 |
if (predicted) { |
| 465 |
dbgui_audio_predicted_note("the current PSG voice registers"); |
| 466 |
} else { |
| 467 |
dbgui_audio_hold_note(); |
| 468 |
} |
| 469 |
} else if (ctl.freeze) { |
| 470 |
ImGui::SameLine(); |
| 471 |
ImGui::TextDisabled("frozen; capture will lapse"); |
| 472 |
} |
| 473 |
|
| 474 |
const float mini_h = ImGui::GetTextLineHeight() * 4.0f; |
| 475 |
if (ImGui::BeginTable("psg_scope_grid", 4, ImGuiTableFlags_SizingStretchSame)) { |
| 476 |
for (int ch = 0; ch < PSG_NUM_CHANNELS; ++ch) { |
| 477 |
const psg_debug_channel &c = snap.ch[ch]; |
| 478 |
const bool silent = voice_silent(c); |
| 479 |
char id[24], overlay[32]; |
| 480 |
snprintf(id, sizeof id, "psg_scope_%d", ch); |
| 481 |
snprintf(overlay, sizeof overlay, "%02d %s%s", ch, waveform_name(c.waveform), |
| 482 |
(ch == *selected_voice) ? " *" : ""); |
| 483 |
|
| 484 |
ImGui::TableNextColumn(); |
| 485 |
dbgui_scope(id, buffers[ch], (int)counts[ch], ImVec2(0, mini_h), |
| 486 |
waveform_color(c.waveform, silent), ctl.window, ctl.gain, ctl.trigger, overlay); |
| 487 |
if (ImGui::IsItemClicked()) { |
| 488 |
*selected_voice = ch; |
| 489 |
} |
| 490 |
} |
| 491 |
ImGui::EndTable(); |
| 492 |
} |
| 493 |
|
| 494 |
if (*selected_voice < 0 || *selected_voice >= PSG_NUM_CHANNELS) { |
| 495 |
*selected_voice = 0; |
| 496 |
} |
| 497 |
|
| 498 |
ImGui::Separator(); |
| 499 |
char overlay[64]; |
| 500 |
snprintf(overlay, sizeof overlay, "Voice %02d %s", *selected_voice, waveform_name(snap.ch[*selected_voice].waveform)); |
| 501 |
ImGui::Text("Selected voice %d", *selected_voice); |
| 502 |
dbgui_scope("psg_scope_selected", buffers[*selected_voice], (int)counts[*selected_voice], |
| 503 |
ImVec2(0, ImGui::GetTextLineHeight() * 8.0f), |
| 504 |
waveform_color(snap.ch[*selected_voice].waveform, voice_silent(snap.ch[*selected_voice])), |
| 505 |
ctl.window, ctl.gain, ctl.trigger, overlay); |
| 506 |
|
| 507 |
ImGui::TextUnformatted("Summed PSG output"); |
| 508 |
dbgui_scope("psg_scope_mix_l", buffers[PSG_SCOPE_MIX_L], (int)counts[PSG_SCOPE_MIX_L], |
| 509 |
ImVec2(0, ImGui::GetTextLineHeight() * 5.0f), IM_COL32(100, 210, 255, 255), |
| 510 |
ctl.window, ctl.gain, ctl.trigger, "Mix L"); |
| 511 |
dbgui_scope("psg_scope_mix_r", buffers[PSG_SCOPE_MIX_R], (int)counts[PSG_SCOPE_MIX_R], |
| 512 |
ImVec2(0, ImGui::GetTextLineHeight() * 5.0f), IM_COL32(255, 170, 105, 255), |
| 513 |
ctl.window, ctl.gain, ctl.trigger, "Mix R"); |
| 514 |
} |
| 515 |
|
| 516 |
|
| 517 |
|
| 518 |
|
| 519 |
void |
| 520 |
psg_panel_render(bool *p_open) |
| 521 |
{ |
| 522 |
static int selected_voice = 0; |
| 523 |
|
| 524 |
if (ImGui::Begin("PSG", p_open)) { |
| 525 |
dbgui_window_zoom("psg"); |
| 526 |
show_status_line(); |
| 527 |
|
| 528 |
if (ImGui::BeginTabBar("psg_tabs", ImGuiTabBarFlags_DrawSelectedOverline)) { |
| 529 |
if (ImGui::BeginTabItem("Voices")) { |
| 530 |
draw_voices_tab(&selected_voice); |
| 531 |
ImGui::EndTabItem(); |
| 532 |
} |
| 533 |
if (ImGui::BeginTabItem("Registers")) { |
| 534 |
draw_registers_tab(); |
| 535 |
ImGui::EndTabItem(); |
| 536 |
} |
| 537 |
if (ImGui::BeginTabItem("Scope")) { |
| 538 |
draw_scope_tab(&selected_voice); |
| 539 |
ImGui::EndTabItem(); |
| 540 |
} |
| 541 |
ImGui::EndTabBar(); |
| 542 |
} |
| 543 |
} |
| 544 |
dbgui_window_end(); |
| 545 |
} |
| 546 |
|
| 547 |
} |
| 548 |
|
| 549 |
static DebugPanelRegistration s_reg("PSG", psg_panel_render, true); |