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