OXYGENHUB
◆ main openmsx / src / sound / SN76489.cc
9.6 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "SN76489.hh"
2
3
#include "DeviceConfig.hh"
4
#include "serialize.hh"
5
6
#include "Math.hh"
7
#include "cstd.hh"
8
#include "narrow.hh"
9
#include "one_of.hh"
10
#include "outer.hh"
11
#include "unreachable.hh"
12
#include "xrange.hh"
13
14
#include <algorithm>
15
#include <array>
16
#include <cassert>
17
#include <cmath>
18
#include <iostream>
19
20
namespace openmsx {
21
22
// The SN76489 divides the clock input by 8, but all users of the clock apply
23
// another divider of 2.
24
static constexpr auto NATIVE_FREQ_INT = unsigned(cstd::round((3579545.0 / 8) / 2));
25
26
static constexpr auto volTable = [] {
27
	std::array<float, 16> result = {};
28
	// 2dB per step -> 0.2, sqrt for amplitude -> 0.5
29
	double factor = cstd::pow<5, 3>(0.1, 0.2 * 0.5);
30
	double out = 32768.0;
31
	for (auto i : xrange(15)) {
32
		result[i] = narrow_cast<float>(cstd::round(out));
33
		out *= factor;
34
	}
35
	result[15] = 0.0f;
36
	return result;
37
}();
38
39
40
// NoiseShifter:
41
42
inline void SN76489::NoiseShifter::initState(unsigned pattern_,
43
                                             unsigned period_)
44
{
45
	pattern = pattern_;
46
	period = period_;
47
	stepsBehind = 0;
48
49
	// Start with only the top bit set.
50
	unsigned allOnes = Math::floodRight(pattern_);
51
	random = allOnes - (allOnes >> 1);
52
}
53
54
inline unsigned SN76489::NoiseShifter::getOutput() const
55
{
56
	return ~random & 1;
57
}
58
59
inline void SN76489::NoiseShifter::advance()
60
{
61
	random = (random >> 1) ^ ((random & 1) ? pattern : 0);
62
}
63
64
inline void SN76489::NoiseShifter::queueAdvance(unsigned steps)
65
{
66
	stepsBehind += steps;
67
	stepsBehind %= period;
68
}
69
70
void SN76489::NoiseShifter::catchUp()
71
{
72
	for (/**/; stepsBehind; stepsBehind--) {
73
		advance();
74
	}
75
}
76
77
template<typename Archive>
78
void SN76489::NoiseShifter::serialize(Archive& ar, unsigned /*version*/)
79
{
80
	// Make sure there are no queued steps, so we don't have to serialize them.
81
	// If we're loading, initState() already set stepsBehind to 0.
82
	if constexpr (!Archive::IS_LOADER) {
83
		catchUp();
84
	}
85
	assert(stepsBehind == 0);
86
87
	// Don't serialize the pattern and noise period; they are initialized by
88
	// the chip class.
89
	ar.serialize("random", random);
90
}
91
92
// Main class:
93
94
SN76489::SN76489(const DeviceConfig& config)
95
	: ResampledSoundDevice(config.getMotherBoard(), "SN76489", "DCSG", 4, NATIVE_FREQ_INT, false)
96
	, debuggable(config.getMotherBoard(), getName())
97
{
98
	if (false) {
99
		std::cout << "volTable:";
100
		for (const auto& e : volTable) std::cout << ' ' << e;
101
		std::cout << '\n';
102
	}
103
	initState();
104
105
	registerSound(config);
106
}
107
108
SN76489::~SN76489()
109
{
110
	unregisterSound();
111
}
112
113
void SN76489::initState()
114
{
115
	registerLatch = 0; // TODO: 3 for Sega.
116
117
	// The Sega integrated versions start zeroed (max volume), while discrete
118
	// chips seem to start with random values (for lack of a reset pin).
119
	// For the user's comfort, we init to silence instead.
120
	for (auto chan : xrange(4)) {
121
		regs[chan * 2 + 0] = 0x0;
122
		regs[chan * 2 + 1] = 0xF;
123
	}
124
	std::ranges::fill(counters, 0);
125
	std::ranges::fill(outputs, 0);
126
127
	initNoise();
128
}
129
130
void SN76489::initNoise()
131
{
132
	// Note: These are the noise patterns for the SN76489A.
133
	//       Other chip variants have different noise patterns.
134
	unsigned period = (regs[6] & 0x4)
135
	                ? ((1 << 15) - 1) // "White" noise: pseudo-random bit sequence.
136
	                : 15;          // "Periodic" noise: produces a square wave with a short duty cycle.
137
	unsigned pattern = (regs[6] & 0x4)
138
	                 ? 0x6000
139
	                 : (1 << (period - 1));
140
	noiseShifter.initState(pattern, period);
141
}
142
143
void SN76489::reset(EmuTime time)
144
{
145
	updateStream(time);
146
	initState();
147
}
148
149
void SN76489::write(uint8_t value, EmuTime time)
150
{
151
	if (value & 0x80) {
152
		registerLatch = (value & 0x70) >> 4;
153
	}
154
	const auto& reg = regs[registerLatch];
155
156
	auto data = [&] -> uint16_t {
157
		switch (registerLatch) {
158
		case 0:
159
		case 2:
160
		case 4:
161
			// Tone period.
162
			if (value & 0x80) {
163
				return uint16_t((reg & 0x3F0) | ((value & 0x0F) << 0));
164
			}  else {
165
				return uint16_t((reg & 0x00F) | ((value & 0x3F) << 4));
166
			}
167
		case 6:
168
			// Noise control.
169
			return value & 0x07;
170
		case 1:
171
		case 3:
172
		case 5:
173
		case 7:
174
			// Attenuation.
175
			return value & 0x0F;
176
		default:
177
			UNREACHABLE;
178
		}
179
	}();
180
181
	// TODO: Warn about access while chip is not ready.
182
183
	writeRegister(registerLatch, data, time);
184
}
185
186
uint16_t SN76489::peekRegister(unsigned reg, EmuTime /*time*/) const
187
{
188
	// Note: None of the register values will change unless a register is
189
	//       written, so we don't need to sync here.
190
	return regs[reg];
191
}
192
193
void SN76489::writeRegister(unsigned reg, uint16_t value, EmuTime time)
194
{
195
	if (reg == 6 || regs[reg] != value) {
196
		updateStream(time);
197
		regs[reg] = value;
198
		if (reg == 6) {
199
			// Every write to register 6 resets the noise shift register.
200
			initNoise();
201
		}
202
	}
203
}
204
205
/*
206
 * Implementation note for noise mode 3:
207
 *
208
 * In this mode, the shift register is advanced when the tone generator #3
209
 * output flips to 1. We emulate this by synthesizing the noise channel using
210
 * that generator before synthesizing its tone channel, but without committing
211
 * the updated state. This ensures that the noise channel and the third tone
212
 * channel are in phase, but do end up in their own separate mixing buffers.
213
 */
214
215
template<bool NOISE> void SN76489::synthesizeChannel(
216
		float*& buffer, unsigned num, unsigned generator)
217
{
218
	unsigned period = [&] {
219
		if (generator == 3) {
220
			// Noise channel using its own generator.
221
			return unsigned(16 << (regs[6] & 3));
222
		} else {
223
			// Tone or noise channel using a tone generator.
224
			unsigned p = regs[2 * generator];
225
			if (p == 0) {
226
				// TODO: Sega variants have a non-flipping output for period 0.
227
				p = 0x400;
228
			}
229
			return p;
230
		}
231
	}();
232
233
	auto output = outputs[generator];
234
	unsigned counter = counters[generator];
235
236
	unsigned channel = NOISE ? 3 : generator;
237
	auto volume = volTable[regs[2 * channel + 1]];
238
	if (volume == 0.0f) {
239
		// Channel is silent, don't synthesize it.
240
		buffer = nullptr;
241
	}
242
	if (buffer) {
243
		// Synthesize channel.
244
		if constexpr (NOISE) {
245
			noiseShifter.catchUp();
246
		}
247
		auto* buf = buffer;
248
		unsigned remaining = num;
249
		while (remaining != 0) {
250
			if (counter == 0) {
251
				output ^= 1;
252
				counter = period;
253
				if (NOISE && output) {
254
					noiseShifter.advance();
255
				}
256
			}
257
			unsigned ticks = std::min(counter, remaining);
258
			if (NOISE ? noiseShifter.getOutput() : output) {
259
				addFill(buf, volume, ticks);
260
			} else {
261
				buf += ticks;
262
			}
263
			counter -= ticks;
264
			remaining -= ticks;
265
		}
266
	} else {
267
		// Advance state without synthesis.
268
		if (counter >= num) {
269
			counter -= num;
270
		} else {
271
			unsigned remaining = num - counter;
272
			output ^= 1; // partial cycle
273
			unsigned cycles = (remaining - 1) / period;
274
			if constexpr (NOISE) {
275
				noiseShifter.queueAdvance((cycles + output) / 2);
276
			}
277
			output ^= cycles & 1; // full cycles
278
			remaining -= cycles * period;
279
			counter = period - remaining;
280
		}
281
	}
282
283
	if (!NOISE || generator == 3) {
284
		outputs[generator] = output;
285
		counters[generator] = narrow_cast<uint16_t>(counter);
286
	}
287
}
288
289
void SN76489::generateChannels(std::span<float*> buffers, unsigned num)
290
{
291
	// Channel 3: noise.
292
	if ((regs[6] & 3) == 3) {
293
		// Use the tone generator #3 (channel 2) output.
294
		synthesizeChannel<true>(buffers[3], num, 2);
295
		// Assume the noise phase counter and output bit keep updating even
296
		// if they are currently not driving the noise shift register.
297
		float* noBuffer = nullptr;
298
		synthesizeChannel<false>(noBuffer, num, 3);
299
	} else {
300
		// Use the channel 3 generator output.
301
		synthesizeChannel<true>(buffers[3], num, 3);
302
	}
303
304
	// Channels 0, 1, 2: tone.
305
	for (auto channel : xrange(3)) {
306
		synthesizeChannel<false>(buffers[channel], num, channel);
307
	}
308
}
309
310
template<typename Archive>
311
void SN76489::serialize(Archive& ar, unsigned version)
312
{
313
	ar.serialize("regs",          regs,
314
	             "registerLatch", registerLatch,
315
	             "counters",      counters,
316
	             "outputs",       outputs);
317
318
	if constexpr (Archive::IS_LOADER) {
319
		// Initialize the computed NoiseShifter members, based on the
320
		// register contents we just loaded, before we load the shift
321
		// register state.
322
		initNoise();
323
	}
324
	// Serialize the noise shift register state as part of the chip: there is
325
	// no need to reflect our class structure in the serialization.
326
	noiseShifter.serialize(ar, version);
327
}
328
INSTANTIATE_SERIALIZE_METHODS(SN76489);
329
330
// Debuggable
331
332
// The frequency registers are 10 bits wide, so we have to split them over
333
// two debuggable entries.
334
static constexpr std::array SN76489_DEBUG_MAP = {
335
	std::array<uint8_t, 2>{0, 0},
336
	std::array<uint8_t, 2>{0, 1},
337
	std::array<uint8_t, 2>{1, 0},
338
	std::array<uint8_t, 2>{2, 0},
339
	std::array<uint8_t, 2>{2, 1},
340
	std::array<uint8_t, 2>{3, 0},
341
	std::array<uint8_t, 2>{4, 0},
342
	std::array<uint8_t, 2>{4, 1},
343
	std::array<uint8_t, 2>{5, 0},
344
	std::array<uint8_t, 2>{6, 0},
345
	std::array<uint8_t, 2>{7, 0},
346
};
347
348
SN76489::Debuggable::Debuggable(MSXMotherBoard& motherBoard_, const std::string& name_)
349
	: SimpleDebuggable(
350
		motherBoard_, name_ + " regs",
351
		"SN76489 regs - note the period regs are split over two entries", 11)
352
{
353
}
354
355
uint8_t SN76489::Debuggable::read(unsigned address, EmuTime time)
356
{
357
	auto [reg, hi] = SN76489_DEBUG_MAP[address];
358
359
	const auto& sn76489 = OUTER(SN76489, debuggable);
360
	uint16_t data = sn76489.peekRegister(reg, time);
361
	return hi ? narrow_cast<uint8_t>(data >> 4)
362
	          : narrow_cast<uint8_t>(data & 0xF);
363
}
364
365
void SN76489::Debuggable::write(unsigned address, uint8_t value, EmuTime time)
366
{
367
	auto reg = SN76489_DEBUG_MAP[address][0];
368
	auto hi  = SN76489_DEBUG_MAP[address][1];
369
370
	auto& sn76489 = OUTER(SN76489, debuggable);
371
	auto data = [&] -> uint16_t {
372
		if (reg == one_of(0, 2, 4)) {
373
			uint16_t d = sn76489.peekRegister(reg, time);
374
			if (hi) {
375
				return uint16_t(((value & 0x3F) << 4) | (d & 0x0F));
376
			} else {
377
				return uint16_t((d & 0x3F0) | (value & 0x0F));
378
			}
379
		} else {
380
			return value & 0x0F;
381
		}
382
	}();
383
	sn76489.writeRegister(reg, data, time);
384
}
385
386
} // namespace openmsx