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