OXYGENHUB
◆ main enhanced-x16-emulator / src / vera_pcm.c
6.6 KiB · 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
#include "vera_pcm.h"
6
#include <stdio.h>
7
#include <string.h>
8
9
static uint8_t  fifo[PCM_FIFO_SIZE];
10
static unsigned fifo_wridx;
11
static unsigned fifo_rdidx;
12
static unsigned fifo_cnt;
13
14
static uint8_t ctrl;
15
static uint8_t rate;
16
static uint8_t loop;
17
18
static uint8_t volume_lut[16] = {0, 1, 2, 3, 4, 5, 6, 8, 11, 14, 18, 23, 30, 38, 49, 64};
19
20
static int16_t cur_l, cur_r;
21
static uint8_t phase;
22
23
// Debugger oscilloscope capture. `scope_armed` counts down in pcm_render() and
24
// is refreshed by pcm_debug_scope_read(), so capture stops by itself once the
25
// debugger stops looking (see vera_pcm.h).
26
#define PCM_SCOPE_ARM_SAMPLES 24576 // ~0.5 s at 48828 Hz
27
28
static int16_t  scope_buf[PCM_SCOPE_STREAMS][PCM_SCOPE_SAMPLES];
29
static unsigned scope_wridx;
30
static unsigned scope_armed;
31
32
static void
33
fifo_reset(void)
34
{
35
	fifo_wridx = 0;
36
	fifo_rdidx = 0;
37
	fifo_cnt   = 0;
38
}
39
40
static void
41
fifo_restart(void)
42
{
43
	fifo_rdidx = 0;
44
	fifo_cnt = fifo_wridx;
45
}
46
47
void
48
pcm_reset(void)
49
{
50
	fifo_reset();
51
	ctrl  = 0;
52
	rate  = 0;
53
	cur_l = 0;
54
	cur_r = 0;
55
	phase = 0;
56
	memset(scope_buf, 0, sizeof(scope_buf));
57
	scope_wridx = 0;
58
}
59
60
void
61
pcm_write_ctrl(uint8_t val)
62
{
63
	if ((val & 0xc0) == 0xc0) {
64
		loop = true;
65
	} else {
66
		loop = false;
67
		if (val & 0x80) {
68
			fifo_reset();
69
		}
70
	}
71
	if (val & 0x40) {
72
		fifo_restart();
73
	}
74
	ctrl = val & 0x3F;
75
}
76
77
uint8_t
78
pcm_read_ctrl(void)
79
{
80
	uint8_t result = ctrl;
81
	if (fifo_cnt == sizeof(fifo) - 1) {
82
		result |= 0x80;
83
	}
84
	if (fifo_cnt == 0) {
85
		result |= 0x40;
86
	}
87
	return result;
88
}
89
90
void
91
pcm_write_rate(uint8_t val)
92
{
93
	rate = (val > 128) ? (256 - val) : val;
94
}
95
96
uint8_t
97
pcm_read_rate(void)
98
{
99
	return rate;
100
}
101
102
void
103
pcm_write_fifo(uint8_t val)
104
{
105
	if (fifo_cnt < sizeof(fifo) - 1) {
106
		fifo[fifo_wridx++] = val;
107
		if (fifo_wridx == sizeof(fifo)) {
108
			fifo_wridx = 0;
109
		}
110
		fifo_cnt++;
111
	}
112
}
113
114
static uint8_t
115
read_fifo()
116
{
117
	static uint8_t result = 0;
118
	if (fifo_cnt == 0) {
119
		return 0;
120
	}
121
	result = fifo[fifo_rdidx++];
122
	if (fifo_rdidx == sizeof(fifo)) {
123
		fifo_rdidx = 0;
124
	}
125
	fifo_cnt--;
126
	return result;
127
}
128
129
bool
130
pcm_is_fifo_almost_empty(void)
131
{
132
	return fifo_cnt < 1024;
133
}
134
135
void
136
pcm_render(int16_t *buf, unsigned num_samples)
137
{
138
	while (num_samples--) {
139
		uint8_t old_phase = phase;
140
		phase += rate;
141
		if ((old_phase & 0x80) != (phase & 0x80)) {
142
			if (fifo_cnt == 0) {
143
				cur_l = 0;
144
				cur_r = 0;
145
			} else {
146
				switch ((ctrl >> 4) & 3) {
147
					case 0: { // mono 8-bit
148
						cur_l = (int16_t)read_fifo() << 8;
149
						cur_r = cur_l;
150
						break;
151
					}
152
					case 1: { // stereo 8-bit
153
						if (fifo_cnt < 2) {
154
							fifo_cnt = 0;
155
							fifo_rdidx = fifo_wridx;
156
						} else {
157
							cur_l = read_fifo() << 8;
158
							cur_r = read_fifo() << 8;
159
						}
160
						break;
161
					}
162
					case 2: { // mono 16-bit
163
						if (fifo_cnt < 2) {
164
							fifo_cnt = 0;
165
							fifo_rdidx = fifo_wridx;
166
						} else {
167
							cur_l = read_fifo();
168
							cur_l |= read_fifo() << 8;
169
							cur_r = cur_l;
170
						}
171
						break;
172
					}
173
					case 3: { // stereo 16-bit
174
						if (fifo_cnt < 4) {
175
							fifo_cnt = 0;
176
							fifo_rdidx = fifo_wridx;
177
						} else {
178
							cur_l = read_fifo();
179
							cur_l |= read_fifo() << 8;
180
							cur_r = read_fifo();
181
							cur_r |= read_fifo() << 8;
182
						}
183
						break;
184
					}
185
				}
186
				if (loop && fifo_cnt == 0) {
187
					fifo_restart();
188
				}
189
			}
190
		}
191
		*(buf++) = (int16_t)((int32_t)cur_l * volume_lut[ctrl & 0xF] / 64);
192
		*(buf++) = (int16_t)((int32_t)cur_r * volume_lut[ctrl & 0xF] / 64);
193
194
		if (scope_armed > 0) {
195
			scope_buf[PCM_SCOPE_L][scope_wridx] = buf[-2];
196
			scope_buf[PCM_SCOPE_R][scope_wridx] = buf[-1];
197
			scope_wridx = (scope_wridx + 1) & (PCM_SCOPE_SAMPLES - 1);
198
			scope_armed--;
199
		}
200
	}
201
}
202
203
// ─── Debugger accessors ─────────────────────────────────────────────────────
204
205
void
206
pcm_debug_get_state(struct pcm_debug_state *out)
207
{
208
	if (out == NULL) {
209
		return;
210
	}
211
	out->ctrl         = ctrl;
212
	out->rate         = rate;
213
	out->loop         = loop != 0;
214
	out->fifo_cnt     = fifo_cnt;
215
	out->fifo_rdidx   = fifo_rdidx;
216
	out->fifo_wridx   = fifo_wridx;
217
	out->fifo_size    = (unsigned)sizeof(fifo);
218
	out->cur_l        = cur_l;
219
	out->cur_r        = cur_r;
220
	out->phase        = phase;
221
	out->almost_empty = pcm_is_fifo_almost_empty();
222
}
223
224
unsigned
225
pcm_debug_peek_fifo(uint8_t *dest, unsigned offset, unsigned len)
226
{
227
	if (dest == NULL || offset >= fifo_cnt) {
228
		return 0;
229
	}
230
	unsigned avail = fifo_cnt - offset;
231
	if (len > avail) {
232
		len = avail;
233
	}
234
	for (unsigned i = 0; i < len; i++) {
235
		dest[i] = fifo[(fifo_rdidx + offset + i) % sizeof(fifo)];
236
	}
237
	return len;
238
}
239
240
unsigned
241
pcm_debug_scope_read(int side, int16_t *dest, unsigned max_samples)
242
{
243
	scope_armed = PCM_SCOPE_ARM_SAMPLES; // (re)arm: the debugger is watching
244
245
	if (side < 0 || side >= PCM_SCOPE_STREAMS || dest == NULL || max_samples == 0) {
246
		return 0;
247
	}
248
249
	unsigned n = (max_samples > PCM_SCOPE_SAMPLES) ? PCM_SCOPE_SAMPLES : max_samples;
250
	unsigned start = (scope_wridx + PCM_SCOPE_SAMPLES - n) & (PCM_SCOPE_SAMPLES - 1);
251
	for (unsigned i = 0; i < n; i++) {
252
		dest[i] = scope_buf[side][(start + i) & (PCM_SCOPE_SAMPLES - 1)];
253
	}
254
	return n;
255
}
256
257
unsigned
258
pcm_debug_scope_predict(int16_t *dest_l, int16_t *dest_r, unsigned num_samples)
259
{
260
	if (dest_l == NULL || dest_r == NULL || num_samples == 0) {
261
		return 0;
262
	}
263
	unsigned n = (num_samples > PCM_SCOPE_SAMPLES) ? PCM_SCOPE_SAMPLES : num_samples;
264
265
	// Everything pcm_render() mutates, saved so the projection leaves no trace.
266
	// ctrl/rate/loop are only touched by register writes, so they stay put.
267
	uint8_t  saved_fifo[PCM_FIFO_SIZE];
268
	memcpy(saved_fifo, fifo, sizeof(fifo));
269
	const unsigned saved_wridx = fifo_wridx;
270
	const unsigned saved_rdidx = fifo_rdidx;
271
	const unsigned saved_cnt   = fifo_cnt;
272
	const int16_t  saved_l     = cur_l;
273
	const int16_t  saved_r     = cur_r;
274
	const uint8_t  saved_phase = phase;
275
	const unsigned saved_armed = scope_armed;
276
	scope_armed = 0; // keep the projection out of the live capture ring
277
278
	// Render through the real path in chunks, so the waveform, the FIFO drain
279
	// order and the loop-restart behaviour all match live playback exactly.
280
	int16_t chunk[128 * 2];
281
	unsigned done = 0;
282
	while (done < n) {
283
		unsigned want = n - done;
284
		if (want > 128) {
285
			want = 128;
286
		}
287
		pcm_render(chunk, want);
288
		for (unsigned i = 0; i < want; i++) {
289
			dest_l[done + i] = chunk[i * 2];
290
			dest_r[done + i] = chunk[i * 2 + 1];
291
		}
292
		done += want;
293
	}
294
295
	memcpy(fifo, saved_fifo, sizeof(fifo));
296
	fifo_wridx  = saved_wridx;
297
	fifo_rdidx  = saved_rdidx;
298
	fifo_cnt    = saved_cnt;
299
	cur_l       = saved_l;
300
	cur_r       = saved_r;
301
	phase       = saved_phase;
302
	scope_armed = saved_armed;
303
	return n;
304
}