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 "serialize.hh"
8 #include "unreachable.hh"
9 #include <algorithm>
10 #include <array>
11 #include <cassert>
12 #include <cmath>
13 #include <iostream>
14 
15 using std::string;
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 (int i = 0; i < 15; i++) {
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 (!ar.isLoader()) {
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 (0) {
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 (unsigned chan = 0; chan < 4; chan++) {
118  regs[chan * 2] = 0;
119  regs[chan * 2 + 1] = 0xF;
120  counters[chan] = 0;
121  outputs[chan] = 0;
122  }
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 pattern, period;
132  if (regs[6] & 0x4) {
133  // "White" noise: pseudo-random bit sequence.
134  pattern = 0x6000;
135  period = (1 << 15) - 1;
136  } else {
137  // "Periodic" noise: produces a square wave with a short duty cycle.
138  period = 15;
139  pattern = 1 << (period - 1);
140  }
141  noiseShifter.initState(pattern, period);
142 }
143 
144 void SN76489::reset(EmuTime::param time)
145 {
146  updateStream(time);
147  initState();
148 }
149 
150 void SN76489::write(byte value, EmuTime::param time)
151 {
152  if (value & 0x80) {
153  registerLatch = (value & 0x70) >> 4;
154  }
155  auto& reg = regs[registerLatch];
156 
157  word data;
158  switch (registerLatch) {
159  case 0:
160  case 2:
161  case 4:
162  // Tone period.
163  if (value & 0x80) {
164  data = (reg & 0x3F0) | (value & 0x0F);
165  } else {
166  data = (reg & 0x00F) | ((value & 0x3F) << 4);
167  }
168  break;
169  case 6:
170  // Noise control.
171  data = value & 0x07;
172  break;
173  case 1:
174  case 3:
175  case 5:
176  case 7:
177  // Attenuation.
178  data = value & 0x0F;
179  break;
180  default:
181  UNREACHABLE;
182  }
183 
184  // TODO: Warn about access while chip is not ready.
185 
186  writeRegister(registerLatch, data, time);
187 }
188 
189 word SN76489::peekRegister(unsigned reg, EmuTime::param /*time*/) const
190 {
191  // Note: None of the register values will change unless a register is
192  // written, so we don't need to sync here.
193  return regs[reg];
194 }
195 
196 void SN76489::writeRegister(unsigned reg, word value, EmuTime::param time)
197 {
198  if (reg == 6 || regs[reg] != value) {
199  updateStream(time);
200  regs[reg] = value;
201  if (reg == 6) {
202  // Every write to register 6 resets the noise shift register.
203  initNoise();
204  }
205  }
206 }
207 
208 /*
209  * Implementation note for noise mode 3:
210  *
211  * In this mode, the shift register is advanced when the tone generator #3
212  * output flips to 1. We emulate this by synthesizing the noise channel using
213  * that generator before synthesizing its tone channel, but without committing
214  * the updated state. This ensures that the noise channel and the third tone
215  * channel are in phase, but do end up in their own separate mixing buffers.
216  */
217 
218 template <bool NOISE> void SN76489::synthesizeChannel(
219  float*& buffer, unsigned num, unsigned generator)
220 {
221  unsigned period;
222  if (generator == 3) {
223  // Noise channel using its own generator.
224  period = 16 << (regs[6] & 3);
225  } else {
226  // Tone or noise channel using a tone generator.
227  period = regs[2 * generator];
228  if (period == 0) {
229  // TODO: Sega variants have a non-flipping output for period 0.
230  period = 0x400;
231  }
232  }
233 
234  auto output = outputs[generator];
235  unsigned counter = counters[generator];
236 
237  unsigned channel = NOISE ? 3 : generator;
238  int volume = volTable[regs[2 * channel + 1]];
239  if (volume == 0) {
240  // Channel is silent, don't synthesize it.
241  buffer = nullptr;
242  }
243  if (buffer) {
244  // Synthesize channel.
245  if (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 (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] = counter;
287  }
288 }
289 
290 void SN76489::generateChannels(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 (unsigned channel = 0; channel < 3; channel++) {
307  synthesizeChannel<false>(buffers[channel], num, channel);
308  }
309 }
310 
311 template<typename Archive>
312 void SN76489::serialize(Archive& ar, unsigned version)
313 {
314  ar.serialize("regs", regs,
315  "registerLatch", registerLatch,
316  "counters", counters,
317  "outputs", outputs);
318 
319  if (ar.isLoader()) {
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.
335 constexpr byte SN76489_DEBUG_MAP[][2] = {
336  {0, 0}, {0, 1}, {1, 0},
337  {2, 0}, {2, 1}, {3, 0},
338  {4, 0}, {4, 1}, {5, 0},
339  {6, 0}, {7, 0}
340 };
341 
342 SN76489::Debuggable::Debuggable(MSXMotherBoard& motherBoard_, const string& name_)
343  : SimpleDebuggable(
344  motherBoard_, name_ + " regs",
345  "SN76489 regs - note the period regs are split over two entries", 11)
346 {
347 }
348 
349 byte SN76489::Debuggable::read(unsigned address, EmuTime::param time)
350 {
351  byte reg = SN76489_DEBUG_MAP[address][0];
352  byte hi = SN76489_DEBUG_MAP[address][1];
353 
354  auto& sn76489 = OUTER(SN76489, debuggable);
355  word data = sn76489.peekRegister(reg, time);
356  return hi ? (data >> 4) : (data & 0xF);
357 }
358 
359 void SN76489::Debuggable::write(unsigned address, byte value, EmuTime::param time)
360 {
361  byte reg = SN76489_DEBUG_MAP[address][0];
362  byte hi = SN76489_DEBUG_MAP[address][1];
363 
364  auto& sn76489 = OUTER(SN76489, debuggable);
365  word data;
366  if (reg == one_of(0, 2, 4)) {
367  data = sn76489.peekRegister(reg, time);
368  if (hi) {
369  data = ((value & 0x3F) << 4) | (data & 0x0F);
370  } else {
371  data = (data & 0x3F0) | (value & 0x0F);
372  }
373  } else {
374  data = value & 0x0F;
375  }
376  sn76489.writeRegister(reg, data, time);
377 }
378 
379 } // namespace openmsx
one_of.hh
openmsx::ResampledSoundDevice
Definition: ResampledSoundDevice.hh:17
gl::min
vecN< N, T > min(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:274
cstd::round
constexpr double round(double x)
Definition: cstd.hh:324
serialize.hh
openmsx::NATIVE_FREQ_INT
constexpr int NATIVE_FREQ_INT
Definition: AY8910.cc:36
cstd.hh
openmsx::DeviceConfig
Definition: DeviceConfig.hh:20
Math::floodRight
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
openmsx::SN76489
SN76489
Definition: SN76489.cc:329
OUTER
#define OUTER(type, member)
Definition: outer.hh:41
UNREACHABLE
#define UNREACHABLE
Definition: unreachable.hh:38
openmsx::Keys::getName
string getName(KeyCode keyCode)
Translate key code to key name.
Definition: Keys.cc:740
openmsx::SoundDevice::registerSound
void registerSound(const DeviceConfig &config)
Registers this sound device with the Mixer.
Definition: SoundDevice.cc:92
one_of
Definition: one_of.hh:7
INSTANTIATE_SERIALIZE_METHODS
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:981
openmsx::SoundDevice::unregisterSound
void unregisterSound()
Unregisters this sound device with the Mixer.
Definition: SoundDevice.cc:133
openmsx::SoundDevice::addFill
static void addFill(float *&buffer, float value, unsigned num)
Adds a number of samples that all have the same value.
Definition: SoundDevice.cc:47
openmsx::SN76489::serialize
void serialize(Archive &ar, unsigned version)
Definition: SN76489.cc:312
outer.hh
openmsx::SN76489::SN76489
SN76489(const DeviceConfig &config)
Definition: SN76489.cc:91
openmsx::SN76489
This class implements the Texas Instruments SN76489 sound chip.
Definition: SN76489.hh:25
openmsx::word
uint16_t word
16 bit unsigned integer
Definition: openmsx.hh:29
openmsx::SN76489_DEBUG_MAP
constexpr byte SN76489_DEBUG_MAP[][2]
Definition: SN76489.cc:335
openmsx::SN76489::generateChannels
void generateChannels(float **buffers, unsigned num) override
Abstract method to generate the actual sound data.
Definition: SN76489.cc:290
openmsx::SN76489::~SN76489
~SN76489()
Definition: SN76489.cc:105
utf8::advance
void advance(octet_iterator &it, distance_type n, octet_iterator end)
Definition: utf8_checked.hh:186
unreachable.hh
DeviceConfig.hh
Math.hh
openmsx::serialize
void serialize(Archive &ar, T &t, unsigned version)
Definition: serialize_core.hh:41
openmsx
This file implemented 3 utility functions:
Definition: Autofire.cc:5
openmsx::SoundDevice::updateStream
void updateStream(EmuTime::param time)
Definition: SoundDevice.cc:138
openmsx::SN76489::write
void write(byte value, EmuTime::param time)
Definition: SN76489.cc:150
SN76489.hh
openmsx::SN76489::reset
void reset(EmuTime::param time)
Definition: SN76489.cc:144