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