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