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