OXYGENHUB
◆ main enhanced-x16-emulator / src / vera_psg.h
4.7 KiB · Objective-C · last changed chore(): initial commit 25 days ago source blame history raw
1
// Commander X16 Emulator
2
// Copyright (c) 2020 Frank van den Hoef
3
// All rights reserved. License: 2-clause BSD
4
5
#pragma once
6
7
#include <stdint.h>
8
#include <stdbool.h>
9
10
#ifdef __cplusplus
11
extern "C" {
12
#endif
13
14
void psg_reset(void);
15
void psg_writereg(uint8_t reg, uint8_t val);
16
void psg_render(int16_t *buf, unsigned num_samples);
17
18
// ─── Debugger accessors ─────────────────────────────────────────────────────
19
// Read-only views of the PSG for the ImGui debugger's PSG panel. All of these
20
// are side-effect free (apart from arming/clearing the debug-only capture state
21
// documented below) and are called from the emulator main thread — the same
22
// thread that runs psg_render() via audio_render() — so no locking is needed.
23
24
#define PSG_NUM_CHANNELS 16
25
#define PSG_NUM_REGS     (PSG_NUM_CHANNELS * 4)
26
27
// Waveform codes, register +3 bits 7-6.
28
enum psg_waveform {
29
	PSG_WF_PULSE = 0,
30
	PSG_WF_SAWTOOTH,
31
	PSG_WF_TRIANGLE,
32
	PSG_WF_NOISE,
33
};
34
35
struct psg_debug_channel {
36
	uint16_t freq;        // 16-bit frequency word
37
	uint8_t  vol_raw;     // volume as written (0-63)
38
	uint16_t vol_lut;     // volume after the 64-entry LUT (0-511)
39
	bool     left, right;
40
	uint8_t  pw;          // pulse width (Pulse) / wave-shaping mask (Saw, Tri)
41
	uint8_t  waveform;    // enum psg_waveform
42
	uint32_t phase;       // live 17-bit phase accumulator
43
	uint16_t noiseval;    // last latched noise sample (0-63)
44
	int16_t  last_sample; // last rendered sample, before L/R panning
45
	uint16_t peak;        // peak |sample| since the previous call (then reset)
46
};
47
48
// Snapshot one voice. `peak` reports the loudest sample since the previous call
49
// and is reset by it, which is exactly what a per-frame level meter wants.
50
// Returns false (and leaves *out untouched) for an out-of-range index.
51
bool psg_debug_get_channel(int ch, struct psg_debug_channel *out);
52
53
// Raw register byte as last written. VERA also mirrors these into video_ram at
54
// $1F9C0+reg, but that mirror survives psg_reset(), so prefer this shadow.
55
uint8_t psg_debug_get_reg(uint8_t reg);
56
57
// The shared 16-bit noise LFSR state.
58
uint16_t psg_debug_get_noise_state(void);
59
60
// ─── Scope capture ──────────────────────────────────────────────────────────
61
// Self-arming per-voice sample capture backing the debugger's oscilloscope.
62
// psg_debug_scope_read() (re)arms capture for roughly half a second; if nothing
63
// reads for that long, capture switches itself off again. psg_render() therefore
64
// costs nothing extra while the Scope tab is hidden, and the panels need no
65
// explicit enable/disable lifecycle.
66
67
#define PSG_SCOPE_SAMPLES  2048             // ring depth, ~42 ms at 48828 Hz
68
#define PSG_SCOPE_MIX_L    PSG_NUM_CHANNELS // summed PSG output, left
69
#define PSG_SCOPE_MIX_R    (PSG_NUM_CHANNELS + 1)
70
#define PSG_SCOPE_STREAMS  (PSG_NUM_CHANNELS + 2)
71
72
// Copy the most recent `max_samples` samples of stream `ch` (a voice index, or
73
// PSG_SCOPE_MIX_L/R) into `dest`, oldest first. All streams share one write
74
// index, so reads of different streams are time-aligned. Returns the sample
75
// count written; always (re)arms capture, even for an invalid stream.
76
unsigned psg_debug_scope_read(int ch, int16_t *dest, unsigned max_samples);
77
78
// Predict what the next `num_samples` would look like, from the current voice
79
// state, WITHOUT advancing the PSG. Fills every stream (indexed as above) of
80
// `dest` and returns the sample count written to each.
81
//
82
// This exists for the debugger: scope capture is self-arming and nothing renders
83
// audio while the machine is paused, so a scope opened at a breakpoint would
84
// otherwise have nothing to draw. The registers fully determine the waveform, so
85
// we can run the real renderer forward on the voice state and show what it is
86
// about to emit. It is a projection of the current registers, not captured
87
// output, so callers must label it as such.
88
//
89
// Uses the same render path as live audio, so the traces cannot drift from it.
90
//
91
// Implementation note for reviewers: this runs the live voice state forward and
92
// then rewinds it, rather than working on a copy. That is safe here because
93
//   * every field render() touches is saved and restored (the state is just
94
//     `channels` and the noise LFSR - there is no hidden or derived state), and
95
//   * psg_render() is only ever called from audio_render() on the main thread,
96
//     the same thread as the debugger, and not at all while paused.
97
// Nothing in the audio path observes the intermediate state.
98
unsigned psg_debug_scope_predict(int16_t (*dest)[PSG_SCOPE_SAMPLES], unsigned num_samples);
99
100
#ifdef __cplusplus
101
}
102
#endif