OXYGENHUB
◆ main enhanced-x16-emulator / src / ymglue.cpp
10.8 KiB · C++ · last changed chore(): initial commit 25 days ago source blame history raw
1
#include "ymglue.h"
2
#include "ymfm_opm.h"
3
#include <cstdint>
4
#include <cstdio>
5
#include <cstring>
6
#include <vector>
7
#include <algorithm>
8
9
// ymfm keeps the FM engine `protected` inside ym2151, but the engine is exactly
10
// what a debugger needs: it exposes debug_operator()/debug_channel() and the
11
// per-channel output() used by the oscilloscope. Publish it via a subclass
12
// rather than patching the vendored source.
13
class ym2151_debuggable : public ymfm::ym2151 {
14
	public:
15
		using ymfm::ym2151::ym2151;
16
		fm_engine &engine() { return m_fm; }
17
};
18
19
class ym2151_interface : public ymfm::ymfm_interface {
20
	public:
21
		ym2151_interface():
22
			m_chip(*this),
23
			m_timers{0, 0},
24
			m_busy_timer{ 0 },
25
			m_irq_status{ false },
26
			m_regs{},
27
			m_keyon{},
28
			m_scope{},
29
			m_scope_wridx{ 0 },
30
			m_scope_armed{ 0 }
31
		{ }
32
		~ym2151_interface() { }
33
34
		virtual void ymfm_sync_mode_write(uint8_t data) override {
35
			m_engine->engine_mode_write(data);
36
		}
37
38
		virtual void ymfm_sync_check_interrupts() override {
39
			m_engine->engine_check_interrupts();
40
		}
41
42
		virtual void ymfm_set_timer(uint32_t tnum, int32_t duration_in_clocks) override {
43
			if (tnum >= 2) return;
44
			m_timers[tnum] = duration_in_clocks;
45
		}
46
47
		virtual void ymfm_set_busy_end(uint32_t clocks) override {
48
			m_busy_timer = clocks;
49
		}
50
51
		virtual bool ymfm_is_busy() override {
52
			return m_busy_timer > 0;
53
		}
54
55
		virtual void ymfm_update_irq(bool asserted) override {
56
			m_irq_status = asserted;
57
		}
58
59
		void update_clocks(int cycles) {
60
			m_busy_timer = std::max(0, m_busy_timer - (64 * cycles));
61
			for (int i = 0; i < 2; ++i) {
62
				if (m_timers[i] > 0) {
63
					m_timers[i] = std::max(0, m_timers[i] - (64 * cycles));
64
					if (m_timers[i] <= 0) {
65
						m_engine->engine_timer_expired(i);
66
					}
67
				}
68
			}	
69
		}
70
71
		void write(uint8_t addr, uint8_t value) {
72
			if (!ymfm_is_busy()) {
73
				m_chip.write_address(addr);
74
				m_chip.write_data(value);
75
				// Shadow only accepted writes, so the debugger's register view
76
				// matches what the chip actually holds.
77
				m_regs[addr] = value;
78
				if (addr == 0x08) {
79
					latch_keyon(value);
80
				}
81
			} else {
82
				printf("YM2151 write received while busy.\n");
83
			}
84
		}
85
86
		void generate(int16_t* output, uint32_t numsamples) {
87
			int s = 0;
88
			int ls, rs;
89
			update_clocks(numsamples);
90
			for (uint32_t i = 0; i < numsamples; i++) {
91
				m_chip.generate(&opm_out);
92
				ls = opm_out.data[0];
93
				rs = opm_out.data[1];
94
				if (ls < -32768) ls = -32768;
95
				if (ls > 32767) ls = 32767;
96
				if (rs < -32768) rs = -32768;
97
				if (rs > 32767) rs = 32767;
98
				output[s++] = ls;
99
				output[s++] = rs;
100
				// Re-test per sample rather than hoisting: capture_sample()
101
				// decrements the countdown, and numsamples is arbitrary, so a
102
				// hoisted test would keep decrementing past zero and wrap the
103
				// unsigned counter — leaving capture armed forever.
104
				if (m_scope_armed > 0) {
105
					capture_sample((int16_t)ls, (int16_t)rs);
106
				}
107
			}
108
		}
109
110
		uint8_t read_status() {
111
			return m_chip.read_status();
112
		}
113
114
		bool irq() {
115
			return m_irq_status;
116
		}
117
118
		// ─── Debugger accessors ─────────────────────────────────────────────
119
120
		uint8_t debug_reg(uint8_t reg) const {
121
			return m_regs[reg];
122
		}
123
124
		uint8_t debug_keyon(int ch) const {
125
			return (ch >= 0 && ch < YM_NUM_CHANNELS) ? m_keyon[ch] : 0;
126
		}
127
128
		bool debug_operator(int op, struct ym_debug_operator *out) {
129
			if (op < 0 || op >= YM_NUM_OPERATORS || out == nullptr) {
130
				return false;
131
			}
132
			auto *fmop = m_chip.engine().debug_operator((uint32_t)op);
133
			if (fmop == nullptr) {
134
				return false;
135
			}
136
			out->eg_state       = (uint8_t)fmop->debug_eg_state();
137
			out->eg_attenuation = fmop->debug_eg_attenuation();
138
			return true;
139
		}
140
141
		bool debug_busy() {
142
			return ymfm_is_busy();
143
		}
144
145
		unsigned debug_scope_read(int ch, int16_t* dest, unsigned max_samples) {
146
			// Always (re)arm: an out-of-range stream still means the debugger
147
			// is watching, and streams may be probed in any order.
148
			m_scope_armed = SCOPE_ARM_SAMPLES;
149
150
			if (ch < 0 || ch >= YM_SCOPE_STREAMS || dest == nullptr || max_samples == 0) {
151
				return 0;
152
			}
153
			unsigned n = (max_samples > YM_SCOPE_SAMPLES) ? YM_SCOPE_SAMPLES : max_samples;
154
			unsigned start = (m_scope_wridx + YM_SCOPE_SAMPLES - n) & (YM_SCOPE_SAMPLES - 1);
155
			for (unsigned i = 0; i < n; i++) {
156
				dest[i] = m_scope[ch][(start + i) & (YM_SCOPE_SAMPLES - 1)];
157
			}
158
			return n;
159
		}
160
161
		// Project the next `num_samples` of output WITHOUT touching the live
162
		// chip in any way.
163
		//
164
		// The live chip's state is snapshotted and loaded into a scratch chip,
165
		// and only the scratch chip is clocked. Envelopes, LFO and operator
166
		// phase therefore continue from exactly where the live chip is, so this
167
		// is a true continuation rather than a re-triggered note.
168
		//
169
		// Restoring back into the live chip would work too - the round trip is
170
		// lossless - but its state is an opaque object graph rather than a
171
		// handful of plain fields, so "everything was put back" is not something
172
		// a reviewer can check by eye the way it is for the PSG and PCM cores.
173
		// ymfm hands us both a spare instance and a save-state, so use them.
174
		unsigned debug_scope_predict(int16_t (*dest)[YM_SCOPE_SAMPLES], unsigned num_samples) {
175
			if (dest == nullptr || num_samples == 0) {
176
				return 0;
177
			}
178
			unsigned n = (num_samples > YM_SCOPE_SAMPLES) ? YM_SCOPE_SAMPLES : num_samples;
179
180
			// Saving is a pure read of the live chip.
181
			std::vector<uint8_t> chip_state;
182
			{
183
				ymfm::ymfm_saved_state saver(chip_state, true);
184
				m_chip.save_restore(saver);
185
			}
186
187
			ym2151_interface &scratch = scratch_chip();
188
			{
189
				ymfm::ymfm_saved_state restorer(chip_state, false);
190
				scratch.m_chip.save_restore(restorer);
191
			}
192
			scratch.m_timers[0]   = m_timers[0];
193
			scratch.m_timers[1]   = m_timers[1];
194
			scratch.m_busy_timer  = m_busy_timer;
195
			scratch.m_irq_status  = m_irq_status;
196
			scratch.m_scope_armed = 0;
197
198
			ymfm::ym2151::output_data out;
199
			for (unsigned i = 0; i < n; i++) {
200
				// Mirror generate()'s per-sample work, including the clock
201
				// bookkeeping, so timer-driven envelope changes show up.
202
				scratch.update_clocks(1);
203
				scratch.m_chip.generate(&out);
204
				int32_t ls = out.data[0], rs = out.data[1];
205
				if (ls < -32768) ls = -32768;
206
				if (ls > 32767) ls = 32767;
207
				if (rs < -32768) rs = -32768;
208
				if (rs > 32767) rs = 32767;
209
				int16_t streams[YM_SCOPE_STREAMS];
210
				scratch.sample_streams((int16_t)ls, (int16_t)rs, streams);
211
				for (int s = 0; s < YM_SCOPE_STREAMS; s++) {
212
					dest[s][i] = streams[s];
213
				}
214
			}
215
			return n;
216
		}
217
218
	private:
219
		static constexpr unsigned SCOPE_ARM_SAMPLES = 28000; // ~0.5 s at 55930 Hz
220
221
		// Spare chip used only to render debugger projections, so nothing the
222
		// debug UI does can perturb the chip that is producing audio. Built on
223
		// first use: most sessions never open the FM scope while paused.
224
		static ym2151_interface &scratch_chip() {
225
			static ym2151_interface *s_scratch = new ym2151_interface();
226
			return *s_scratch;
227
		}
228
229
		// $08 bits 3-6 select operators in the OPM's key-on order M1, C1, M2, C2,
230
		// which is NOT the order the $40-$FF register slots use (M1, M2, C1, C2).
231
		// Re-pack into slot order so callers index key-on state the same way they
232
		// index operators and registers.
233
		void latch_keyon(uint8_t value) {
234
			const uint8_t kon   = (value >> 3) & 0x0F;
235
			uint8_t       slots = 0;
236
			if (kon & 0x01) slots |= 1u << 0; // M1 -> slot 0
237
			if (kon & 0x02) slots |= 1u << 2; // C1 -> slot 2
238
			if (kon & 0x04) slots |= 1u << 1; // M2 -> slot 1
239
			if (kon & 0x08) slots |= 1u << 3; // C2 -> slot 3
240
			m_keyon[value & 7] = slots;
241
		}
242
243
		// Isolate each channel by re-running the engine's output stage with a
244
		// single-channel mask. This does NOT disturb the audio: output() only
245
		// reads operator phase/envelope state (compute_volume is const and pure)
246
		// and writes the mutable m_feedback_in, which clock() consumes on the
247
		// next sample — recomputing it from unchanged inputs yields the same
248
		// value.
249
		void sample_streams(int16_t mix_l, int16_t mix_r, int16_t out[YM_SCOPE_STREAMS]) {
250
			ymfm::ym2151::output_data one;
251
			for (int c = 0; c < YM_NUM_CHANNELS; c++) {
252
				m_chip.engine().output(one.clear(), 0, 32767, 1u << c);
253
				int32_t v = one.data[0] + one.data[1];
254
				if (v < -32768) v = -32768;
255
				if (v > 32767) v = 32767;
256
				out[c] = (int16_t)v;
257
			}
258
			out[YM_SCOPE_MIX_L] = mix_l;
259
			out[YM_SCOPE_MIX_R] = mix_r;
260
		}
261
262
		// Only reached while capture is armed.
263
		void capture_sample(int16_t mix_l, int16_t mix_r) {
264
			int16_t streams[YM_SCOPE_STREAMS];
265
			sample_streams(mix_l, mix_r, streams);
266
			for (int i = 0; i < YM_SCOPE_STREAMS; i++) {
267
				m_scope[i][m_scope_wridx] = streams[i];
268
			}
269
			m_scope_wridx = (m_scope_wridx + 1) & (YM_SCOPE_SAMPLES - 1);
270
			m_scope_armed--;
271
		}
272
273
		ym2151_debuggable m_chip;
274
		int32_t m_timers[2];
275
		int32_t m_busy_timer;
276
		bool m_irq_status;
277
278
		uint8_t m_regs[YM_NUM_REGS];
279
		uint8_t m_keyon[YM_NUM_CHANNELS];
280
281
		int16_t  m_scope[YM_SCOPE_STREAMS][YM_SCOPE_SAMPLES];
282
		unsigned m_scope_wridx;
283
		unsigned m_scope_armed;
284
285
		ymfm::ym2151::output_data opm_out;
286
};
287
288
namespace {
289
	ym2151_interface opm_iface;
290
	bool initialized = false;
291
}
292
293
extern "C" {
294
	void YM_Create(int clock) {
295
		// clock is fixed at 3.579545MHz
296
	}
297
298
	void YM_init(int sample_rate, int frame_rate) {
299
		// args are ignored
300
		initialized = true;
301
	}
302
303
	void YM_stream_update(uint16_t* output, uint32_t numsamples) {
304
		if (initialized) opm_iface.generate((int16_t*)output, numsamples);
305
	}
306
307
	void YM_write_reg(uint8_t reg, uint8_t val) {
308
		if (initialized) opm_iface.write(reg, val);
309
	}
310
311
	uint8_t YM_read_status() {
312
		if (initialized)
313
			return opm_iface.read_status();
314
		else
315
			return 0x00; // prevent programs that wait for the busy flag to clear from locking up (emulator compromise)
316
	}
317
318
	bool YM_irq() {
319
		if (initialized)
320
			return opm_iface.irq();
321
		else
322
			return false;
323
	}
324
325
	// ─── Debugger accessors ─────────────────────────────────────────────────
326
327
	uint8_t YM_debug_get_reg(uint8_t reg) {
328
		return opm_iface.debug_reg(reg);
329
	}
330
331
	uint8_t YM_debug_get_keyon(int ch) {
332
		return opm_iface.debug_keyon(ch);
333
	}
334
335
	bool YM_debug_get_operator(int op, struct ym_debug_operator *out) {
336
		if (!initialized) return false;
337
		return opm_iface.debug_operator(op, out);
338
	}
339
340
	uint8_t YM_debug_get_status() {
341
		// read_status() has no side effects on the OPM (it just ORs the busy
342
		// flag into the engine's status byte), so it is safe to poll.
343
		return initialized ? opm_iface.read_status() : 0x00;
344
	}
345
346
	bool YM_debug_is_busy() {
347
		return initialized ? opm_iface.debug_busy() : false;
348
	}
349
350
	unsigned YM_debug_scope_read(int ch, int16_t* dest, unsigned max_samples) {
351
		return opm_iface.debug_scope_read(ch, dest, max_samples);
352
	}
353
354
	unsigned YM_debug_scope_predict(int16_t (*dest)[YM_SCOPE_SAMPLES], unsigned num_samples) {
355
		return initialized ? opm_iface.debug_scope_predict(dest, num_samples) : 0;
356
	}
357
}