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 
17 namespace openmsx {
18 
19 // The SN76489 divides the clock input by 8, but all users of the clock apply
20 // another divider of 2.
21 constexpr auto NATIVE_FREQ_INT = unsigned(cstd::round((3579545.0 / 8) / 2));
22 
23 static 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 
39 inline 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 
51 inline unsigned SN76489::NoiseShifter::getOutput() const
52 {
53  return ~random & 1;
54 }
55 
57 {
58  random = (random >> 1) ^ ((random & 1) ? pattern : 0);
59 }
60 
61 inline void SN76489::NoiseShifter::queueAdvance(unsigned steps)
62 {
63  stepsBehind += steps;
64  stepsBehind %= period;
65 }
66 
67 void SN76489::NoiseShifter::catchUp()
68 {
69  for (; stepsBehind; stepsBehind--) {
70  advance();
71  }
72 }
73 
74 template<typename Archive>
75 void 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 {
107  unregisterSound();
108 }
109 
110 void 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 
127 void 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 
140 void SN76489::reset(EmuTime::param time)
141 {
142  updateStream(time);
143  initState();
144 }
145 
146 void 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 
183 word 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 
190 void 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 
212 template<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 
286 void 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 
307 template<typename Archive>
308 void 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.
331 constexpr 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 
338 SN76489::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 
345 byte 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 
355 void 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:139
static void addFill(float *&buffer, float value, unsigned num)
Adds a number of samples that all have the same value.
Definition: SoundDevice.cc:44
void unregisterSound()
Unregisters this sound device with the Mixer.
Definition: SoundDevice.cc:134
void registerSound(const DeviceConfig &config)
Registers this sound device with the Mixer.
Definition: SoundDevice.cc:89
constexpr T floodRight(T x) noexcept
Returns the smallest number of the form 2^n-1 that is greater or equal to the given number.
Definition: Math.hh:69
constexpr double round(double x)
Definition: cstd.hh:355
constexpr vecN< N, T > min(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:269
std::string getName(KeyCode keyCode)
Translate key code to key name.
Definition: Keys.cc:741
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
void fill(ForwardRange &&range, const T &value)
Definition: ranges.hh:226
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:998
#define UNREACHABLE
Definition: unreachable.hh:38
constexpr auto xrange(T e)
Definition: xrange.hh:155