openMSX
SCC.cc
Go to the documentation of this file.
1//-----------------------------------------------------------------------------
2//
3// On Mon, 24 Feb 2003, Jon De Schrijder wrote:
4//
5// I've done some measurements with the scope on the output of the SCC.
6// I didn't do timing tests, only amplitude checks:
7//
8// I know now for sure, the amplitude calculation works as follows:
9//
10// AmpOut=640+AmpA+AmpB+AmpC+AmpD+AmpE
11//
12// range AmpOut (11 bits positive number=SCC digital output): [+40...+1235]
13//
14// AmpA="((SampleValue*VolA) AND #7FF0) div 16"
15// AmpB="((SampleValue*VolB) AND #7FF0) div 16"
16// AmpC="((SampleValue*VolC) AND #7FF0) div 16"
17// AmpD="((SampleValue*VolD) AND #7FF0) div 16"
18// AmpE="((SampleValue*VolE) AND #7FF0) div 16"
19//
20// Setting the enable bit to zero, corresponds with VolX=0.
21//
22// SampleValue range [-128...+127]
23// VolX range [0..15]
24//
25// Notes:
26// * SampleValue*VolX is calculated (signed multiplication) and the lower 4
27// bits are dropped (both in case the value is positive or negative), before
28// the addition of the 5 values is done. This was tested by setting
29// SampleValue=+1 and VolX=15 of different channels. The resulting AmpOut=640,
30// indicating that the 4 lower bits were dropped *before* the addition.
31//
32//-----------------------------------------------------------------------------
33//
34// On Mon, 14 Apr 2003, Manuel Pazos wrote
35//
36// I have some info about SCC/SCC+ that I hope you find useful. It is about
37// "Mode Setting Register", also called "Deformation Register" Here it goes:
38//
39// bit0: 4 bits frequency (%XXXX00000000). Equivalent to
40// (normal frequency >> 8) bits0-7 are ignored
41// bit1: 8 bits frequency (%0000XXXXXXXX) bits8-11 are ignored
42// bit2:
43// bit3:
44// bit4:
45// bit5: wave data is played from beginning when frequency is changed
46// bit6: rotate all waves data. You can't write to them. Rotation speed
47// =3.58Mhz / (channel i frequency + 1)
48// bit7: rotate channel 4 wave data. You can't write to that channel
49// data.ONLY works in MegaROM SCC (not in SCC+)
50//
51// If bit7 and bit6 are set, only channel 1-3 wave data rotates . You can't
52// write to ANY wave data. And there is a weird behaviour in this setting. It
53// seems SCC sound is corrupted in anyway with MSX databus or so. Try to
54// activate them (with proper waves, freqs, and vol.) and execute DIR command
55// on DOS. You will hear "noise" This seems to be fixed in SCC+
56//
57// Reading Mode Setting Register, is equivalent to write #FF to it.
58//
59// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
60//
61// Additions:
62// - Setting both bit0 and bit1 is equivalent to setting only bit1
63// - A rotation goes like this:
64// waveData[0:31] = waveData[1:31].waveData[0]
65// - Channel 4-5 rotation speed is set by channel 5 freq (channel 4 freq
66// is ignored for rotation)
67//
68// Also see this MRC thread:
69// http://www.msx.org/forumtopicl7875.html
70//
71//-----------------------------------------------------------------------------
72//
73// On Sat, 09 Sep 2005, NYYRIKKI wrote (MRC post)
74//
75// ...
76//
77// One important thing to know is that change of volume is not implemented
78// immediately in SCC. Normally it is changed when next byte from sample memory
79// is played, but writing value to frequency causes current byte to be started
80// again. As in this example we write values very quickly to frequency registers
81// the internal sample counter does not actually move at all.
82//
83// Third method is a variation of first method. As we don't know where SCC is
84// playing, let's update the whole sample memory with one and same new value.
85// To make sample rate not variable in low sample rates we first stop SCC from
86// reading sample memory. This can be done by writing value less than 9 to
87// frequency. Now we can update sample RAM so, that output does not change.
88// After sample RAM has been updated, we start SCC internal counter so that
89// value (where ever the counter was) is sent to output. This routine can be
90// found below as example 3.
91//
92// ...
93//
94//
95//
96// Something completely different: the SCC+ is actually called SCC-I.
97//-----------------------------------------------------------------------------
98
99#include "SCC.hh"
100#include "DeviceConfig.hh"
101#include "cstd.hh"
102#include "enumerate.hh"
103#include "outer.hh"
104#include "ranges.hh"
105#include "serialize.hh"
106#include "unreachable.hh"
107#include "xrange.hh"
108#include <array>
109#include <cmath>
110
111namespace openmsx {
112
113static constexpr auto INPUT_RATE = unsigned(cstd::round(3579545.0 / 32));
114
115static constexpr auto calcDescription(SCC::Mode mode)
116{
117 return (mode == SCC::Mode::Real) ? static_string_view("Konami SCC")
118 : static_string_view("Konami SCC+");
119}
120
121SCC::SCC(const std::string& name_, const DeviceConfig& config,
122 EmuTime::param time, Mode mode)
124 config.getMotherBoard(), name_, calcDescription(mode), 5, INPUT_RATE, false)
125 , debuggable(config.getMotherBoard(), getName())
126 , deformTimer(time)
127 , currentMode(mode)
128{
129 // Make valgrind happy
130 ranges::fill(orgPeriod, 0);
131
132 powerUp(time);
133 registerSound(config);
134}
135
137{
139}
140
141void SCC::powerUp(EmuTime::param time)
142{
143 // Power on values, tested by enen (log from IRC #openmsx):
144 //
145 // <enen> wouter_: i did an scc poweron values test, deform=0,
146 // amplitude=full, channelEnable=0, period=under 8
147 // ...
148 // <wouter_> did you test the value of the waveforms as well?
149 // ...
150 // <enen> filled with $FF, some bits cleared but that seems random
151
152 // Initialize ch_enable, deform (initialize this before period)
153 reset(time);
154
155 // Initialize waveforms (initialize before volumes)
156 for (auto& w1 : wave) {
157 ranges::fill(w1, ~0);
158 }
159 // Initialize volume (initialize this before period)
160 for (auto i : xrange(5)) {
161 setFreqVol(i + 10, 15, time);
162 }
163 // Actual initial value is difficult to measure, assume zero
164 // (initialize before period)
165 ranges::fill(pos, 0);
166
167 // Initialize period (sets members orgPeriod, period, incr, count, out)
168 for (auto i : xrange(2 * 5)) {
169 setFreqVol(i, 0, time);
170 }
171}
172
173void SCC::reset(EmuTime::param /*time*/)
174{
175 if (currentMode != Mode::Real) {
177 }
178
179 setDeformRegHelper(0);
180 ch_enable = 0;
181}
182
183void SCC::setMode(Mode newMode)
184{
185 if (currentMode == Mode::Real) {
186 assert(newMode == Mode::Real);
187 } else {
188 assert(newMode != Mode::Real);
189 }
190 currentMode = newMode;
191}
192
193uint8_t SCC::readMem(uint8_t addr, EmuTime::param time)
194{
195 // Deform-register locations:
196 // SCC_Real: 0xE0..0xFF
197 // SCC_Compatible: 0xC0..0xDF
198 // SCC_plusmode: 0xC0..0xDF
199 if (((currentMode == Mode::Real) && (addr >= 0xE0)) ||
200 ((currentMode != Mode::Real) && (0xC0 <= addr) && (addr < 0xE0))) {
201 setDeformReg(0xFF, time);
202 }
203 return peekMem(addr, time);
204}
205
206uint8_t SCC::peekMem(uint8_t address, EmuTime::param time) const
207{
208 switch (currentMode) {
209 case Mode::Real:
210 if (address < 0x80) {
211 // 0x00..0x7F : read wave form 1..4
212 return readWave(address >> 5, address, time);
213 } else {
214 // 0x80..0x9F : freq volume block, write only
215 // 0xA0..0xDF : no function
216 // 0xE0..0xFF : deformation register
217 return 0xFF;
218 }
219 case Mode::Compatible:
220 if (address < 0x80) {
221 // 0x00..0x7F : read wave form 1..4
222 return readWave(address >> 5, address, time);
223 } else if (address < 0xA0) {
224 // 0x80..0x9F : freq volume block
225 return 0xFF;
226 } else if (address < 0xC0) {
227 // 0xA0..0xBF : read wave form 5
228 return readWave(4, address, time);
229 } else {
230 // 0xC0..0xDF : deformation register
231 // 0xE0..0xFF : no function
232 return 0xFF;
233 }
234 case Mode::Plus:
235 if (address < 0xA0) {
236 // 0x00..0x9F : read wave form 1..5
237 return readWave(address >> 5, address, time);
238 } else {
239 // 0xA0..0xBF : freq volume block
240 // 0xC0..0xDF : deformation register
241 // 0xE0..0xFF : no function
242 return 0xFF;
243 }
244 default:
246 }
247}
248
249uint8_t SCC::readWave(unsigned channel, unsigned address, EmuTime::param time) const
250{
251 if (!rotate[channel]) {
252 return wave[channel][address & 0x1F];
253 } else {
254 unsigned ticks = deformTimer.getTicksTill(time);
255 unsigned periodCh = ((channel == 3) &&
256 (currentMode != Mode::Plus) &&
257 ((deformValue & 0xC0) == 0x40))
258 ? 4 : channel;
259 unsigned shift = ticks / (period[periodCh] + 1);
260 return wave[channel][(address + shift) & 0x1F];
261 }
262}
263
264
265uint8_t SCC::getFreqVol(unsigned address) const
266{
267 address &= 0x0F;
268 if (address < 0x0A) {
269 // get frequency
270 unsigned channel = address / 2;
271 if (address & 1) {
272 return narrow_cast<uint8_t>(orgPeriod[channel] >> 8);
273 } else {
274 return narrow_cast<uint8_t>(orgPeriod[channel] & 0xFF);
275 }
276 } else if (address < 0x0F) {
277 // get volume
278 return volume[address - 0xA];
279 } else {
280 // get enable-bits
281 return ch_enable;
282 }
283}
284
285void SCC::writeMem(uint8_t address, uint8_t value, EmuTime::param time)
286{
287 updateStream(time);
288
289 switch (currentMode) {
290 case Mode::Real:
291 if (address < 0x80) {
292 // 0x00..0x7F : write wave form 1..4
293 writeWave(address >> 5, address, value);
294 } else if (address < 0xA0) {
295 // 0x80..0x9F : freq volume block
296 setFreqVol(address, value, time);
297 } else if (address < 0xE0) {
298 // 0xA0..0xDF : no function
299 } else {
300 // 0xE0..0xFF : deformation register
301 setDeformReg(value, time);
302 }
303 break;
304 case Mode::Compatible:
305 if (address < 0x80) {
306 // 0x00..0x7F : write wave form 1..4
307 writeWave(address >> 5, address, value);
308 } else if (address < 0xA0) {
309 // 0x80..0x9F : freq volume block
310 setFreqVol(address, value, time);
311 } else if (address < 0xC0) {
312 // 0xA0..0xBF : ignore write wave form 5
313 } else if (address < 0xE0) {
314 // 0xC0..0xDF : deformation register
315 setDeformReg(value, time);
316 } else {
317 // 0xE0..0xFF : no function
318 }
319 break;
320 case Mode::Plus:
321 if (address < 0xA0) {
322 // 0x00..0x9F : write wave form 1..5
323 writeWave(address >> 5, address, value);
324 } else if (address < 0xC0) {
325 // 0xA0..0xBF : freq volume block
326 setFreqVol(address, value, time);
327 } else if (address < 0xE0) {
328 // 0xC0..0xDF : deformation register
329 setDeformReg(value, time);
330 } else {
331 // 0xE0..0xFF : no function
332 }
333 break;
334 default:
336 }
337}
338
339float SCC::getAmplificationFactorImpl() const
340{
341 return 1.0f / 128.0f;
342}
343
344static constexpr float adjust(int8_t wav, uint8_t vol)
345{
346 // The result is an integer value, but we store it as a float because
347 // then we need fewer int->float conversion (compared to converting in
348 // generateChannels()).
349 return float((int(wav) * vol) >> 4);
350}
351
352void SCC::writeWave(unsigned channel, unsigned address, uint8_t value)
353{
354 // write to channel 5 only possible in SCC+ mode
355 assert(channel < 5);
356 assert((channel != 4) || (currentMode == Mode::Plus));
357
358 if (!readOnly[channel]) {
359 unsigned p = address & 0x1F;
360 auto sValue = narrow_cast<int8_t>(value);
361 wave[channel][p] = sValue;
362 volAdjustedWave[channel][p] = adjust(sValue, volume[channel]);
363 if ((currentMode != Mode::Plus) && (channel == 3)) {
364 // copy waveform 4 -> waveform 5
365 wave[4][p] = wave[3][p];
366 volAdjustedWave[4][p] = adjust(sValue, volume[4]);
367 }
368 }
369}
370
371void SCC::setFreqVol(unsigned address, uint8_t value, EmuTime::param time)
372{
373 address &= 0x0F; // region is visible twice
374 if (address < 0x0A) {
375 // change frequency
376 unsigned channel = address / 2;
377 unsigned per =
378 (address & 1)
379 ? ((value & 0xF) << 8) | (orgPeriod[channel] & 0xFF)
380 : (orgPeriod[channel] & 0xF00) | (value & 0xFF);
381 orgPeriod[channel] = per;
382 if (deformValue & 2) {
383 // 8 bit frequency
384 per &= 0xFF;
385 } else if (deformValue & 1) {
386 // 4 bit frequency
387 per >>= 8;
388 }
389 period[channel] = per;
390 incr[channel] = (per <= 8) ? 0 : 32;
391 count[channel] = 0; // reset to begin of byte
392 if (deformValue & 0x20) {
393 pos[channel] = 0; // reset to begin of waveform
394 // also 'rotation' mode (confirmed by test based on
395 // Artag's SCC sample player)
396 deformTimer.advance(time);
397 }
398 // after a freq change, update the output
399 out[channel] = volAdjustedWave[channel][pos[channel]];
400 } else if (address < 0x0F) {
401 // change volume
402 unsigned channel = address - 0x0A;
403 volume[channel] = value & 0xF;
404 for (auto i : xrange(32)) {
405 volAdjustedWave[channel][i] =
406 adjust(wave[channel][i], volume[channel]);
407 }
408 } else {
409 // change enable-bits
410 ch_enable = value;
411 }
412}
413
414void SCC::setDeformReg(uint8_t value, EmuTime::param time)
415{
416 if (value == deformValue) {
417 return;
418 }
419 deformTimer.advance(time);
420 setDeformRegHelper(value);
421}
422
423void SCC::setDeformRegHelper(uint8_t value)
424{
425 deformValue = value;
426 if (currentMode != Mode::Real) {
427 value &= ~0x80;
428 }
429 switch (value & 0xC0) {
430 case 0x00:
431 ranges::fill(rotate, false);
432 ranges::fill(readOnly, false);
433 break;
434 case 0x40:
435 ranges::fill(rotate, true);
436 ranges::fill(readOnly, true);
437 break;
438 case 0x80:
439 for (auto i : xrange(3)) {
440 rotate[i] = false;
441 readOnly[i] = false;
442 }
443 for (auto i : xrange(3, 5)) {
444 rotate[i] = true;
445 readOnly[i] = true;
446 }
447 break;
448 case 0xC0:
449 for (auto i : xrange(3)) {
450 rotate[i] = true;
451 readOnly[i] = true;
452 }
453 for (auto i : xrange(3, 5)) {
454 rotate[i] = false;
455 readOnly[i] = true;
456 }
457 break;
458 default:
460 }
461}
462
463void SCC::generateChannels(std::span<float*> bufs, unsigned num)
464{
465 unsigned enable = ch_enable;
466 for (unsigned i = 0; i < 5; ++i, enable >>= 1) {
467 if ((enable & 1) && (volume[i] || (out[i] != 0.0f))) {
468 auto out2 = out[i];
469 unsigned count2 = count[i];
470 unsigned pos2 = pos[i];
471 unsigned incr2 = incr[i];
472 unsigned period2 = period[i] + 1;
473 for (auto j : xrange(num)) {
474 bufs[i][j] += out2;
475 count2 += incr2;
476 // Note: only for very small periods
477 // this will take more than 1 iteration
478 while (count2 >= period2) [[unlikely]] {
479 count2 -= period2;
480 pos2 = (pos2 + 1) % 32;
481 out2 = volAdjustedWave[i][pos2];
482 }
483 }
484 out[i] = out2;
485 count[i] = count2;
486 pos[i] = pos2;
487 } else {
488 bufs[i] = nullptr; // channel muted
489 // Update phase counter.
490 unsigned newCount = count[i] + num * incr[i];
491 count[i] = newCount % (period[i] + 1);
492 pos[i] = (pos[i] + newCount / (period[i] + 1)) % 32;
493 // Channel stays off until next waveform index.
494 out[i] = 0.0f;
495 }
496 }
497}
498
499
500// Debuggable
501
502SCC::Debuggable::Debuggable(MSXMotherBoard& motherBoard_, const std::string& name_)
503 : SimpleDebuggable(motherBoard_, name_ + " SCC",
504 "SCC registers in SCC+ format", 0x100)
505{
506}
507
508uint8_t SCC::Debuggable::read(unsigned address, EmuTime::param time)
509{
510 auto& scc = OUTER(SCC, debuggable);
511 if (address < 0xA0) {
512 // read wave form 1..5
513 return scc.readWave(address >> 5, address, time);
514 } else if (address < 0xC0) {
515 // freq volume block
516 return scc.getFreqVol(address);
517 } else if (address < 0xE0) {
518 // peek deformation register
519 return scc.deformValue;
520 } else {
521 return 0xFF;
522 }
523}
524
525void SCC::Debuggable::write(unsigned address, uint8_t value, EmuTime::param time)
526{
527 auto& scc = OUTER(SCC, debuggable);
528 if (address < 0xA0) {
529 // read wave form 1..5
530 scc.writeWave(address >> 5, address, value);
531 } else if (address < 0xC0) {
532 // freq volume block
533 scc.setFreqVol(address, value, time);
534 } else if (address < 0xE0) {
535 // deformation register
536 scc.setDeformReg(value, time);
537 } else {
538 // ignore
539 }
540}
541
542
543static constexpr std::initializer_list<enum_string<SCC::Mode>> chipModeInfo = {
544 { "Real", SCC::Mode::Real },
545 { "Compatible", SCC::Mode::Compatible },
546 { "Plus", SCC::Mode::Plus },
547};
549
550template<typename Archive>
551void SCC::serialize(Archive& ar, unsigned /*version*/)
552{
553 ar.serialize("mode", currentMode,
554 "period", orgPeriod,
555 "volume", volume,
556 "ch_enable", ch_enable,
557 "deformTimer", deformTimer,
558 "deform", deformValue);
559 // multi-dimensional arrays are not directly support by the
560 // serialization framework, maybe in the future. So for now
561 // manually loop over the channels.
562 std::array<char, 6> tag = {'w', 'a', 'v', 'e', 'X', 0};
563 for (auto [channel, wv] : enumerate(wave)) {
564 tag[4] = char('1' + channel);
565 ar.serialize(tag.data(), wv); // signed char
566 }
567
568 if constexpr (Archive::IS_LOADER) {
569 // recalculate volAdjustedWave
570 for (auto channel : xrange(5)) {
571 for (auto p : xrange(32)) {
572 volAdjustedWave[channel][p] =
573 adjust(wave[channel][p], volume[channel]);
574 }
575 }
576
577 // recalculate rotate[5] and readOnly[5]
578 setDeformRegHelper(deformValue);
579
580 // recalculate incr[5] and period[5]
581 // this also (possibly) changes count[5], pos[5] and out[5]
582 // as an unwanted side-effect, so (de)serialize those later
583 // Don't use current time, but instead use deformTimer, to
584 // avoid changing the value of deformTimer.
585 EmuTime::param time = deformTimer.getTime();
586 for (auto channel : xrange(5)) {
587 unsigned per = orgPeriod[channel];
588 setFreqVol(2 * channel + 0, (per & 0x0FF) >> 0, time);
589 setFreqVol(2 * channel + 1, (per & 0xF00) >> 8, time);
590 }
591 }
592
593 // call to setFreqVol() modifies these variables, see above
594 ar.serialize("count", count,
595 "pos", pos,
596 "out", out); // note: changed int->float, but no need to bump serialize-version
597}
599
600} // namespace openmsx
constexpr EmuTime::param getTime() const
Gets the time at which the last clock tick occurred.
Definition Clock.hh:46
constexpr void advance(EmuTime::param e)
Advance this clock in time until the last tick which is not past the given time.
Definition Clock.hh:110
constexpr unsigned getTicksTill(EmuTime::param e) const
Calculate the number of ticks for this clock until the given time.
Definition Clock.hh:58
void setMode(Mode newMode)
Definition SCC.cc:183
void serialize(Archive &ar, unsigned version)
Definition SCC.cc:551
void powerUp(EmuTime::param time)
Definition SCC.cc:141
uint8_t readMem(uint8_t address, EmuTime::param time)
Definition SCC.cc:193
void reset(EmuTime::param time)
Definition SCC.cc:173
uint8_t peekMem(uint8_t address, EmuTime::param time) const
Definition SCC.cc:206
void writeMem(uint8_t address, uint8_t value, EmuTime::param time)
Definition SCC.cc:285
SCC(const std::string &name, const DeviceConfig &config, EmuTime::param time, Mode mode=Mode::Real)
Definition SCC.cc:121
void updateStream(EmuTime::param time)
void unregisterSound()
Unregisters this sound device with the Mixer.
void registerSound(const DeviceConfig &config)
Registers this sound device with the Mixer.
static_string_view
constexpr auto enumerate(Iterable &&iterable)
Heavily inspired by Nathan Reed's blog post: Python-Like enumerate() In C++17 http://reedbeta....
Definition enumerate.hh:28
constexpr double round(double x)
Definition cstd.hh:247
This file implemented 3 utility functions:
Definition Autofire.cc:11
constexpr void fill(ForwardRange &&range, const T &value)
Definition ranges.hh:305
#define OUTER(type, member)
Definition outer.hh:42
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
#define SERIALIZE_ENUM(TYPE, INFO)
#define UNREACHABLE
constexpr auto xrange(T e)
Definition xrange.hh:132