OXYGENHUB
◆ main enhanced-x16-emulator / src / vera_psg.c
7.0 KiB · C · last changed chore(): initial commit 25 days ago source blame history raw
9120e874 Andy McCall 1
// Commander X16 Emulator
2
// Copyright (c) 2020 Frank van den Hoef
3
// All rights reserved. License: 2-clause BSD
4
5
#include "vera_psg.h"
6
7
#include <stdbool.h>
8
#include <string.h>
9
10
enum waveform {
11
	WF_PULSE = 0,
12
	WF_SAWTOOTH,
13
	WF_TRIANGLE,
14
	WF_NOISE,
15
};
16
17
struct channel {
18
	uint16_t freq;
19
	uint16_t volume;
20
	bool     left, right;
21
	uint8_t  pw;
22
	uint8_t  waveform;
23
24
	uint16_t noiseval;
25
	uint32_t phase;
26
27
	// Debug-only mirrors, maintained by render(). Cheap enough (two compares
28
	// and a store per voice per sample) to keep unconditional, which lets the
29
	// debugger show live levels without arming the scope capture.
30
	int16_t  last_sample;
31
	uint16_t peak;
32
};
33
34
static struct channel channels[16];
35
36
// Raw register bytes exactly as written, for the debugger's register view.
37
static uint8_t reg_shadow[PSG_NUM_REGS];
38
39
static uint16_t volume_lut[64] = {
40
	  0,                                           4,   8,  12,
41
	 16,  17,  18,  20,  21,  22,  23,  25,  26,  28,  30,  31,
42
	 33,  35,  37,  40,  42,  45,  47,  50,  53,  56,  60,  63,
43
	 67,  71,  75,  80,  85,  90,  95, 101, 107, 113, 120, 127,
44
	135, 143, 151, 160, 170, 180, 191, 202, 214, 227, 241, 255,
45
	270, 286, 303, 321, 341, 361, 382, 405, 429, 455, 482, 511
46
};
47
48
static uint16_t noise_state;
49
50
// Debugger oscilloscope capture. One shared write index keeps every stream
51
// time-aligned. `scope_armed` counts down in render() and is refreshed by
52
// psg_debug_scope_read(), so capture stops by itself once the debugger stops
53
// looking (see vera_psg.h).
54
#define PSG_SCOPE_ARM_SAMPLES 24576 // ~0.5 s at 48828 Hz
55
56
static int16_t  scope_buf[PSG_SCOPE_STREAMS][PSG_SCOPE_SAMPLES];
57
static unsigned scope_wridx;
58
static unsigned scope_armed;
59
60
void
61
psg_reset(void)
62
{
63
	memset(channels, 0, sizeof(channels));
64
	memset(reg_shadow, 0, sizeof(reg_shadow));
65
	memset(scope_buf, 0, sizeof(scope_buf));
66
	scope_wridx = 0;
67
	noise_state = 1;
68
}
69
70
void
71
psg_writereg(uint8_t reg, uint8_t val)
72
{
73
	reg &= 0x3f;
74
75
	reg_shadow[reg] = val;
76
77
	int ch  = reg / 4;
78
	int idx = reg & 3;
79
80
	switch (idx) {
81
		case 0: channels[ch].freq = (channels[ch].freq & 0xFF00) | val; break;
82
		case 1: channels[ch].freq = (channels[ch].freq & 0x00FF) | (val << 8); break;
83
		case 2: {
84
			channels[ch].right  = (val & 0x80) != 0;
85
			channels[ch].left   = (val & 0x40) != 0;
86
			channels[ch].volume = volume_lut[val & 0x3F];
87
			break;
88
		}
89
		case 3: {
90
			channels[ch].pw       = val & 0x3F;
91
			channels[ch].waveform = val >> 6;
92
			break;
93
		}
94
	}
95
}
96
97
static void
98
render(int16_t *left, int16_t *right)
99
{
100
	int16_t l = 0;
101
	int16_t r = 0;
102
103
	const bool capture = scope_armed > 0;
104
105
	for (int i = 0; i < 16; i++) {
106
		// In FPGA implementation, noise values are generated every system clock and
107
		// the channel update is run sequentially. So, even if both two channels are
108
		// fetching a noise value in the same sample, they should have different values
109
		noise_state = (noise_state << 1) | (((noise_state >> 1) ^ (noise_state >> 2) ^ (noise_state >> 4) ^ (noise_state >> 15)) & 1);
110
111
		struct channel *ch = &channels[i];
112
113
		uint32_t new_phase = (ch->left || ch->right) ? ((ch->phase + ch->freq) & 0x1FFFF) : 0;
114
		if ((ch->phase & 0x10000) && !(new_phase & 0x10000)) {
115
			ch->noiseval = (noise_state >> 1) & 0x3F;
116
		}
117
		ch->phase = new_phase;
118
119
		uint32_t v = 0;
120
		switch (ch->waveform) {
121
			case WF_PULSE: v = ((ch->phase >> 10) > ch->pw) ? 0 : 0x3F; break;
122
    		case WF_SAWTOOTH: v = (ch->phase >> 11) ^ ((ch->pw ^ 0x3f) & 0x3f); break;
123
			case WF_TRIANGLE: v = ((ch->phase & 0x10000) ? (~(ch->phase >> 10) & 0x3F) : ((ch->phase >> 10) & 0x3F)) ^ ((ch->pw ^ 0x3f) & 0x3f); break;		
124
			case WF_NOISE: v = ch->noiseval; break;
125
		}
126
		int16_t sv = (v ^ 0x20);
127
		if (sv & 0x20) {
128
			sv |= 0xFFC0;
129
		}
130
131
		int16_t val = sv * ch->volume;
132
133
		ch->last_sample = val;
134
		uint16_t mag = (uint16_t)(val < 0 ? -val : val);
135
		if (mag > ch->peak) {
136
			ch->peak = mag;
137
		}
138
		if (capture) {
139
			scope_buf[i][scope_wridx] = val;
140
		}
141
142
		if (ch->left) {
143
			l += val >> 3;
144
		}
145
		if (ch->right) {
146
			r += val >> 3;
147
		}
148
	}
149
150
	*left  = l;
151
	*right = r;
152
153
	if (capture) {
154
		scope_buf[PSG_SCOPE_MIX_L][scope_wridx] = l;
155
		scope_buf[PSG_SCOPE_MIX_R][scope_wridx] = r;
156
		scope_wridx = (scope_wridx + 1) & (PSG_SCOPE_SAMPLES - 1);
157
		scope_armed--;
158
	}
159
}
160
161
void
162
psg_render(int16_t *buf, unsigned num_samples)
163
{
164
	while (num_samples--) {
165
		render(&buf[0], &buf[1]);
166
		buf += 2;
167
	}
168
}
169
170
// ─── Debugger accessors ─────────────────────────────────────────────────────
171
172
bool
173
psg_debug_get_channel(int ch, struct psg_debug_channel *out)
174
{
175
	if (ch < 0 || ch >= PSG_NUM_CHANNELS || out == NULL) {
176
		return false;
177
	}
178
	struct channel *c = &channels[ch];
179
180
	out->freq        = c->freq;
181
	out->vol_raw     = reg_shadow[ch * 4 + 2] & 0x3F;
182
	out->vol_lut     = c->volume;
183
	out->left        = c->left;
184
	out->right       = c->right;
185
	out->pw          = c->pw;
186
	out->waveform    = c->waveform;
187
	out->phase       = c->phase;
188
	out->noiseval    = c->noiseval;
189
	out->last_sample = c->last_sample;
190
	out->peak        = c->peak;
191
192
	c->peak = 0; // peak-since-last-read, so a level meter decays naturally
193
	return true;
194
}
195
196
uint8_t
197
psg_debug_get_reg(uint8_t reg)
198
{
199
	return reg_shadow[reg & 0x3F];
200
}
201
202
uint16_t
203
psg_debug_get_noise_state(void)
204
{
205
	return noise_state;
206
}
207
208
unsigned
209
psg_debug_scope_read(int ch, int16_t *dest, unsigned max_samples)
210
{
211
	// Always (re)arm first: an out-of-range stream still means "the debugger is
212
	// watching", and the caller may legitimately probe streams in any order.
213
	scope_armed = PSG_SCOPE_ARM_SAMPLES;
214
215
	if (ch < 0 || ch >= PSG_SCOPE_STREAMS || dest == NULL || max_samples == 0) {
216
		return 0;
217
	}
218
219
	unsigned n = (max_samples > PSG_SCOPE_SAMPLES) ? PSG_SCOPE_SAMPLES : max_samples;
220
	unsigned start = (scope_wridx + PSG_SCOPE_SAMPLES - n) & (PSG_SCOPE_SAMPLES - 1);
221
	for (unsigned i = 0; i < n; i++) {
222
		dest[i] = scope_buf[ch][(start + i) & (PSG_SCOPE_SAMPLES - 1)];
223
	}
224
	return n;
225
}
226
227
unsigned
228
psg_debug_scope_predict(int16_t (*dest)[PSG_SCOPE_SAMPLES], unsigned num_samples)
229
{
230
	if (dest == NULL || num_samples == 0) {
231
		return 0;
232
	}
233
	unsigned n = (num_samples > PSG_SCOPE_SAMPLES) ? PSG_SCOPE_SAMPLES : num_samples;
234
235
	// Drive the real renderer over a saved-and-restored copy of the state. Going
236
	// through render() rather than reimplementing the waveform maths is the whole
237
	// point: the projection can never disagree with what the voices would emit.
238
	//
239
	// render() writes last_sample for every voice on every sample, so the
240
	// per-voice traces come out of that and the mix comes out of its return
241
	// values. Capture is forced off so this never lands in the live scope ring.
242
	struct channel saved_channels[16];
243
	memcpy(saved_channels, channels, sizeof(channels));
244
	const uint16_t saved_noise = noise_state;
245
	const unsigned saved_armed = scope_armed;
246
	scope_armed = 0;
247
248
	for (unsigned i = 0; i < n; i++) {
249
		int16_t l = 0, r = 0;
250
		render(&l, &r);
251
		for (int c = 0; c < PSG_NUM_CHANNELS; c++) {
252
			dest[c][i] = channels[c].last_sample;
253
		}
254
		dest[PSG_SCOPE_MIX_L][i] = l;
255
		dest[PSG_SCOPE_MIX_R][i] = r;
256
	}
257
258
	memcpy(channels, saved_channels, sizeof(channels));
259
	noise_state = saved_noise;
260
	scope_armed = saved_armed;
261
	return n;
262
}