OXYGENHUB
◆ main openmsx / src / sound / SCC.cc
16.9 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 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
101
#include "DeviceConfig.hh"
102
103
#include "cstd.hh"
104
#include "enumerate.hh"
105
#include "outer.hh"
106
#include "serialize.hh"
107
#include "unreachable.hh"
108
#include "xrange.hh"
109
110
#include <algorithm>
111
#include <array>
112
#include <cmath>
113
114
namespace openmsx {
115
116
static constexpr auto INPUT_RATE = unsigned(cstd::round(3579545.0 / 32));
117
118
static constexpr auto calcDescription(SCC::Mode mode)
119
{
120
	return (mode == SCC::Mode::Real) ? static_string_view("Konami SCC")
121
	                                 : static_string_view("Konami SCC+");
122
}
123
124
SCC::SCC(const std::string& name_, const DeviceConfig& config,
125
         EmuTime time, Mode mode)
126
	: ResampledSoundDevice(
127
		config.getMotherBoard(), name_, calcDescription(mode), 5, INPUT_RATE, false)
128
	, debuggable(config.getMotherBoard(), getName())
129
	, deformTimer(time)
130
	, currentMode(mode)
131
{
132
	// Make valgrind happy
133
	std::ranges::fill(orgPeriod, 0);
134
135
	powerUp(time);
136
	registerSound(config);
137
}
138
139
SCC::~SCC()
140
{
141
	unregisterSound();
142
}
143
144
void SCC::powerUp(EmuTime time)
145
{
146
	// Power on values, tested by enen (log from IRC #openmsx):
147
	//
148
	//  <enen>    wouter_: i did an scc poweron values test, deform=0,
149
	//            amplitude=full, channelEnable=0, period=under 8
150
	//    ...
151
	//  <wouter_> did you test the value of the waveforms as well?
152
	//    ...
153
	//  <enen>    filled with $FF, some bits cleared but that seems random
154
155
	// Initialize ch_enable, deform (initialize this before period)
156
	reset(time);
157
158
	// Initialize waveforms (initialize before volumes)
159
	for (auto& w1 : wave) {
160
		std::ranges::fill(w1, ~0);
161
	}
162
	// Initialize volume (initialize this before period)
163
	for (auto i : xrange(5)) {
164
		setFreqVol(i + 10, 15, time);
165
	}
166
	// Actual initial value is difficult to measure, assume zero
167
	// (initialize before period)
168
	std::ranges::fill(pos, 0);
169
170
	// Initialize period (sets members orgPeriod, period, incr, count, out)
171
	for (auto i : xrange(2 * 5)) {
172
		setFreqVol(i, 0, time);
173
	}
174
}
175
176
void SCC::reset(EmuTime /*time*/)
177
{
178
	if (currentMode != Mode::Real) {
179
		setMode(Mode::Compatible);
180
	}
181
182
	setDeformRegHelper(0);
183
	ch_enable = 0;
184
}
185
186
void SCC::setMode(Mode newMode)
187
{
188
	if (currentMode == Mode::Real) {
189
		assert(newMode == Mode::Real);
190
	} else {
191
		assert(newMode != Mode::Real);
192
	}
193
	currentMode = newMode;
194
}
195
196
uint8_t SCC::readMem(uint8_t addr, EmuTime time)
197
{
198
	// Deform-register locations:
199
	//   SCC_Real:       0xE0..0xFF
200
	//   SCC_Compatible: 0xC0..0xDF
201
	//   SCC_plusmode:   0xC0..0xDF
202
	if (((currentMode == Mode::Real) && (addr >= 0xE0)) ||
203
	    ((currentMode != Mode::Real) && (0xC0 <= addr) && (addr < 0xE0))) {
204
		setDeformReg(0xFF, time);
205
	}
206
	return peekMem(addr, time);
207
}
208
209
uint8_t SCC::peekMem(uint8_t address, EmuTime time) const
210
{
211
	switch (currentMode) {
212
	case Mode::Real:
213
		if (address < 0x80) {
214
			// 0x00..0x7F : read wave form 1..4
215
			return readWave(address >> 5, address, time);
216
		} else {
217
			// 0x80..0x9F : freq volume block, write only
218
			// 0xA0..0xDF : no function
219
			// 0xE0..0xFF : deformation register
220
			return 0xFF;
221
		}
222
	case Mode::Compatible:
223
		if (address < 0x80) {
224
			// 0x00..0x7F : read wave form 1..4
225
			return readWave(address >> 5, address, time);
226
		} else if (address < 0xA0) {
227
			// 0x80..0x9F : freq volume block
228
			return 0xFF;
229
		} else if (address < 0xC0) {
230
			// 0xA0..0xBF : read wave form 5
231
			return readWave(4, address, time);
232
		} else {
233
			// 0xC0..0xDF : deformation register
234
			// 0xE0..0xFF : no function
235
			return 0xFF;
236
		}
237
	case Mode::Plus:
238
		if (address < 0xA0) {
239
			// 0x00..0x9F : read wave form 1..5
240
			return readWave(address >> 5, address, time);
241
		} else {
242
			// 0xA0..0xBF : freq volume block
243
			// 0xC0..0xDF : deformation register
244
			// 0xE0..0xFF : no function
245
			return 0xFF;
246
		}
247
	default:
248
		UNREACHABLE;
249
	}
250
}
251
252
uint8_t SCC::readWave(unsigned channel, unsigned address, EmuTime time) const
253
{
254
	if (!rotate[channel]) {
255
		return wave[channel][address & 0x1F];
256
	} else {
257
		unsigned ticks = deformTimer.getTicksTill(time);
258
		unsigned periodCh = ((channel == 3) &&
259
		                     (currentMode != Mode::Plus) &&
260
		                     ((deformValue & 0xC0) == 0x40))
261
		                  ? 4 : channel;
262
		unsigned shift = ticks / (period[periodCh] + 1);
263
		return wave[channel][(address + shift) & 0x1F];
264
	}
265
}
266
267
268
uint8_t SCC::getFreqVol(unsigned address) const
269
{
270
	address &= 0x0F;
271
	if (address < 0x0A) {
272
		// get frequency
273
		unsigned channel = address / 2;
274
		if (address & 1) {
275
			return narrow_cast<uint8_t>(orgPeriod[channel] >> 8);
276
		} else {
277
			return narrow_cast<uint8_t>(orgPeriod[channel] & 0xFF);
278
		}
279
	} else if (address < 0x0F) {
280
		// get volume
281
		return volume[address - 0xA];
282
	} else {
283
		// get enable-bits
284
		return ch_enable;
285
	}
286
}
287
288
void SCC::writeMem(uint8_t address, uint8_t value, EmuTime time)
289
{
290
	updateStream(time);
291
292
	switch (currentMode) {
293
	case Mode::Real:
294
		if (address < 0x80) {
295
			// 0x00..0x7F : write wave form 1..4
296
			writeWave(address >> 5, address, value);
297
		} else if (address < 0xA0) {
298
			// 0x80..0x9F : freq volume block
299
			setFreqVol(address, value, time);
300
		} else if (address < 0xE0) {
301
			// 0xA0..0xDF : no function
302
		} else {
303
			// 0xE0..0xFF : deformation register
304
			setDeformReg(value, time);
305
		}
306
		break;
307
	case Mode::Compatible:
308
		if (address < 0x80) {
309
			// 0x00..0x7F : write wave form 1..4
310
			writeWave(address >> 5, address, value);
311
		} else if (address < 0xA0) {
312
			// 0x80..0x9F : freq volume block
313
			setFreqVol(address, value, time);
314
		} else if (address < 0xC0) {
315
			// 0xA0..0xBF : ignore write wave form 5
316
		} else if (address < 0xE0) {
317
			// 0xC0..0xDF : deformation register
318
			setDeformReg(value, time);
319
		} else {
320
			// 0xE0..0xFF : no function
321
		}
322
		break;
323
	case Mode::Plus:
324
		if (address < 0xA0) {
325
			// 0x00..0x9F : write wave form 1..5
326
			writeWave(address >> 5, address, value);
327
		} else if (address < 0xC0) {
328
			// 0xA0..0xBF : freq volume block
329
			setFreqVol(address, value, time);
330
		} else if (address < 0xE0) {
331
			// 0xC0..0xDF : deformation register
332
			setDeformReg(value, time);
333
		} else {
334
			// 0xE0..0xFF : no function
335
		}
336
		break;
337
	default:
338
		UNREACHABLE;
339
	}
340
}
341
342
float SCC::getAmplificationFactorImpl() const
343
{
344
	return 1.0f / 128.0f;
345
}
346
347
static constexpr float adjust(int8_t wav, uint8_t vol)
348
{
349
	// The result is an integer value, but we store it as a float because
350
	// then we need fewer int->float conversion (compared to converting in
351
	// generateChannels()).
352
	return float((int(wav) * vol) >> 4);
353
}
354
355
void SCC::writeWave(unsigned channel, unsigned address, uint8_t value)
356
{
357
	// write to channel 5 only possible in SCC+ mode
358
	assert(channel < 5);
359
	assert((channel != 4) || (currentMode == Mode::Plus));
360
361
	if (!readOnly[channel]) {
362
		unsigned p = address & 0x1F;
363
		auto sValue = narrow_cast<int8_t>(value);
364
		wave[channel][p] = sValue;
365
		volAdjustedWave[channel][p] = adjust(sValue, volume[channel]);
366
		if ((currentMode != Mode::Plus) && (channel == 3)) {
367
			// copy waveform 4 -> waveform 5
368
			wave[4][p] = wave[3][p];
369
			volAdjustedWave[4][p] = adjust(sValue, volume[4]);
370
		}
371
	}
372
}
373
374
void SCC::setFreqVol(unsigned address, uint8_t value, EmuTime time)
375
{
376
	address &= 0x0F; // region is visible twice
377
	if (address < 0x0A) {
378
		// change frequency
379
		unsigned channel = address / 2;
380
		unsigned per =
381
			  (address & 1)
382
			? ((value & 0xF) << 8) | (orgPeriod[channel] & 0xFF)
383
			: (orgPeriod[channel] & 0xF00) | (value & 0xFF);
384
		orgPeriod[channel] = per;
385
		if (deformValue & 2) {
386
			// 8 bit frequency
387
			per &= 0xFF;
388
		} else if (deformValue & 1) {
389
			// 4 bit frequency
390
			per >>= 8;
391
		}
392
		period[channel] = per;
393
		incr[channel] = (per <= 8) ? 0 : 32;
394
		count[channel] = 0; // reset to begin of byte
395
		if (deformValue & 0x20) {
396
			pos[channel] = 0; // reset to begin of waveform
397
			// also 'rotation' mode (confirmed by test based on
398
			// Artag's SCC sample player)
399
			deformTimer.advance(time);
400
		}
401
		// after a freq change, update the output
402
		out[channel] = volAdjustedWave[channel][pos[channel]];
403
	} else if (address < 0x0F) {
404
		// change volume
405
		unsigned channel = address - 0x0A;
406
		volume[channel] = value & 0xF;
407
		for (auto i : xrange(32)) {
408
			volAdjustedWave[channel][i] =
409
				adjust(wave[channel][i], volume[channel]);
410
		}
411
	} else {
412
		// change enable-bits
413
		ch_enable = value;
414
	}
415
}
416
417
void SCC::setDeformReg(uint8_t value, EmuTime time)
418
{
419
	if (value == deformValue) {
420
		return;
421
	}
422
	deformTimer.advance(time);
423
	setDeformRegHelper(value);
424
}
425
426
void SCC::setDeformRegHelper(uint8_t value)
427
{
428
	deformValue = value;
429
	if (currentMode != Mode::Real) {
430
		value &= ~0x80;
431
	}
432
	switch (value & 0xC0) {
433
	case 0x00:
434
		std::ranges::fill(rotate, false);
435
		std::ranges::fill(readOnly, false);
436
		break;
437
	case 0x40:
438
		std::ranges::fill(rotate, true);
439
		std::ranges::fill(readOnly, true);
440
		break;
441
	case 0x80:
442
		for (auto i : xrange(3)) {
443
			rotate[i] = false;
444
			readOnly[i] = false;
445
		}
446
		for (auto i : xrange(3, 5)) {
447
			rotate[i] = true;
448
			readOnly[i] = true;
449
		}
450
		break;
451
	case 0xC0:
452
		for (auto i : xrange(3)) {
453
			rotate[i] = true;
454
			readOnly[i] = true;
455
		}
456
		for (auto i : xrange(3, 5)) {
457
			rotate[i] = false;
458
			readOnly[i] = true;
459
		}
460
		break;
461
	default:
462
		UNREACHABLE;
463
	}
464
}
465
466
void SCC::generateChannels(std::span<float*> bufs, unsigned num)
467
{
468
	unsigned enable = ch_enable;
469
	for (unsigned i = 0; i < 5; ++i, enable >>= 1) {
470
		if ((enable & 1) && (volume[i] || (out[i] != 0.0f))) {
471
			auto out2 = out[i];
472
			unsigned count2 = count[i];
473
			unsigned pos2 = pos[i];
474
			unsigned incr2 = incr[i];
475
			unsigned period2 = period[i] + 1;
476
			for (auto j : xrange(num)) {
477
				bufs[i][j] += out2;
478
				count2 += incr2;
479
				// Note: only for very small periods
480
				//       this will take more than 1 iteration
481
				while (count2 >= period2) [[unlikely]] {
482
					count2 -= period2;
483
					pos2 = (pos2 + 1) % 32;
484
					out2 = volAdjustedWave[i][pos2];
485
				}
486
			}
487
			out[i] = out2;
488
			count[i] = count2;
489
			pos[i] = pos2;
490
		} else {
491
			bufs[i] = nullptr; // channel muted
492
			// Update phase counter.
493
			unsigned newCount = count[i] + num * incr[i];
494
			count[i] = newCount % (period[i] + 1);
495
			pos[i] = (pos[i] + newCount / (period[i] + 1)) % 32;
496
			// Channel stays off until next waveform index.
497
			out[i] = 0.0f;
498
		}
499
	}
500
}
501
502
503
// Debuggable
504
505
SCC::Debuggable::Debuggable(MSXMotherBoard& motherBoard_, const std::string& name_)
506
	: SimpleDebuggable(motherBoard_, name_ + " SCC",
507
	                   "SCC registers in SCC+ format", 0x100)
508
{
509
}
510
511
uint8_t SCC::Debuggable::read(unsigned address, EmuTime time)
512
{
513
	const auto& scc = OUTER(SCC, debuggable);
514
	if (address < 0xA0) {
515
		// read wave form 1..5
516
		return scc.readWave(address >> 5, address, time);
517
	} else if (address < 0xC0) {
518
		// freq volume block
519
		return scc.getFreqVol(address);
520
	} else if (address < 0xE0) {
521
		// peek deformation register
522
		return scc.deformValue;
523
	} else {
524
		return 0xFF;
525
	}
526
}
527
528
void SCC::Debuggable::write(unsigned address, uint8_t value, EmuTime time)
529
{
530
	auto& scc = OUTER(SCC, debuggable);
531
	if (address < 0xA0) {
532
		// read wave form 1..5
533
		scc.writeWave(address >> 5, address, value);
534
	} else if (address < 0xC0) {
535
		// freq volume block
536
		scc.setFreqVol(address, value, time);
537
	} else if (address < 0xE0) {
538
		// deformation register
539
		scc.setDeformReg(value, time);
540
	} else {
541
		// ignore
542
	}
543
}
544
545
546
static constexpr auto chipModeInfo = std::to_array<enum_string<SCC::Mode>>({
547
	{ "Real",       SCC::Mode::Real       },
548
	{ "Compatible", SCC::Mode::Compatible },
549
	{ "Plus",       SCC::Mode::Plus   },
550
});
551
SERIALIZE_ENUM(SCC::Mode, chipModeInfo);
552
553
template<typename Archive>
554
void SCC::serialize(Archive& ar, unsigned /*version*/)
555
{
556
	ar.serialize("mode",        currentMode,
557
	             "period",      orgPeriod,
558
	             "volume",      volume,
559
	             "ch_enable",   ch_enable,
560
	             "deformTimer", deformTimer,
561
	             "deform",      deformValue);
562
	// multi-dimensional arrays are not directly support by the
563
	// serialization framework, maybe in the future. So for now
564
	// manually loop over the channels.
565
	std::array<char, 6> tag = {'w', 'a', 'v', 'e', 'X', 0};
566
	for (auto [channel, wv] : enumerate(wave)) {
567
		tag[4] = char('1' + channel);
568
		ar.serialize(tag.data(), wv); // signed char
569
	}
570
571
	if constexpr (Archive::IS_LOADER) {
572
		// recalculate volAdjustedWave
573
		for (auto channel : xrange(5)) {
574
			for (auto p : xrange(32)) {
575
				volAdjustedWave[channel][p] =
576
					adjust(wave[channel][p], volume[channel]);
577
			}
578
		}
579
580
		// recalculate rotate[5] and readOnly[5]
581
		setDeformRegHelper(deformValue);
582
583
		// recalculate incr[5] and period[5]
584
		//  this also (possibly) changes count[5], pos[5] and out[5]
585
		//  as an unwanted side-effect, so (de)serialize those later
586
		// Don't use current time, but instead use deformTimer, to
587
		// avoid changing the value of deformTimer.
588
		EmuTime time = deformTimer.getTime();
589
		for (auto channel : xrange(5)) {
590
			unsigned per = orgPeriod[channel];
591
			setFreqVol(2 * channel + 0, (per & 0x0FF) >> 0, time);
592
			setFreqVol(2 * channel + 1, (per & 0xF00) >> 8, time);
593
		}
594
	}
595
596
	// call to setFreqVol() modifies these variables, see above
597
	ar.serialize("count", count,
598
	             "pos",   pos,
599
	             "out",   out); // note: changed int->float, but no need to bump serialize-version
600
}
601
INSTANTIATE_SERIALIZE_METHODS(SCC);
602
603
} // namespace openmsx