OXYGENHUB
◆ main openmsx / src / sound / VLM5030.cc
16.8 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
/*
2
    vlm5030.c
3
4
    VLM5030 emulator
5
6
    Written by Tatsuyuki Satoh
7
    Based on TMS5220 simulator (tms5220.c)
8
9
  note:
10
    memory read cycle(==sampling rate) = 122.9u(440clock)
11
    interpolator (LC8109 = 2.5ms)      = 20 * samples(125us)
12
    frame time (20ms)                  =  4 * interpolator
13
    9bit DAC is composed of 5bit Physical and 3bitPWM.
14
15
  todo:
16
    Noise Generator circuit without 'rand()' function.
17
18
----------- command format (Analytical result) ----------
19
20
1)end of speech (8bit)
21
:00000011:
22
23
2)silent some frame (8bit)
24
:????SS01:
25
26
SS : number of silent frames
27
   00 = 2 frame
28
   01 = 4 frame
29
   10 = 6 frame
30
   11 = 8 frame
31
32
3)-speech frame (48bit)
33
function:   6th  :  5th   :   4th  :   3rd  :   2nd  : 1st    :
34
end     :   ---  :  ---   :   ---  :   ---  :   ---  :00000011:
35
silent  :   ---  :  ---   :   ---  :   ---  :   ---  :0000SS01:
36
speech  :11111122:22233334:44455566:67778889:99AAAEEE:EEPPPPP0:
37
38
EEEEE  : energy : volume 0=off,0x1f=max
39
PPPPP  : pitch  : 0=noise , 1=fast,0x1f=slow
40
111111 : K1     : 48=off
41
22222  : K2     : 0=off,1=+min,0x0f=+max,0x10=off,0x11=+max,0x1f=-min
42
                : 16 == special function??
43
3333   : K3     : 0=off,1=+min,0x07=+max,0x08=-max,0x0f=-min
44
4444   : K4     :
45
555    : K5     : 0=off,1=+min,0x03=+max,0x04=-max,0x07=-min
46
666    : K6     :
47
777    : K7     :
48
888    : K8     :
49
999    : K9     :
50
AAA    : K10    :
51
52
 ---------- chirp table information ----------
53
54
DAC PWM cycle == 88system clock , (11clock x 8 pattern) = 40.6KHz
55
one chirp     == 5 x PWM cycle == 440system clock(8,136Hz)
56
57
chirp  0   : volume 10- 8 : with filter
58
chirp  1   : volume  8- 6 : with filter
59
chirp  2   : volume  6- 4 : with filter
60
chirp  3   : volume   4   : no filter ??
61
chirp  4- 5: volume  4- 2 : with filter
62
chirp  6-11: volume  2- 0 : with filter
63
chirp 12-..: volume   0   : silent
64
65
 ---------- digital output information ----------
66
 when ME pin = high , some status output to A0..15 pins
67
68
  A0..8   : DAC output value (abs)
69
  A9      : DAC sign flag , L=minus,H=Plus
70
  A10     : energy reload flag (pitch pulse)
71
  A11..15 : unknown
72
73
  [DAC output value(signed 6bit)] = A9 ? A0..8 : -(A0..8)
74
75
*/
76
77
#include "VLM5030.hh"
78
79
#include "DeviceConfig.hh"
80
#include "FileOperations.hh"
81
#include "XMLElement.hh"
82
#include "serialize.hh"
83
84
#include "cstd.hh"
85
#include "narrow.hh"
86
#include "one_of.hh"
87
#include "random.hh"
88
#include "ranges.hh"
89
#include "xrange.hh"
90
91
#include <algorithm>
92
#include <array>
93
#include <cmath>
94
95
namespace openmsx {
96
97
// interpolator per frame
98
static constexpr int FR_SIZE = 4;
99
// samples per interpolator
100
static constexpr uint8_t IP_SIZE_SLOWER = 240 / FR_SIZE;
101
static constexpr uint8_t IP_SIZE_SLOW   = 200 / FR_SIZE;
102
static constexpr uint8_t IP_SIZE_NORMAL = 160 / FR_SIZE;
103
static constexpr uint8_t IP_SIZE_FAST   = 120 / FR_SIZE;
104
static constexpr uint8_t IP_SIZE_FASTER =  80 / FR_SIZE;
105
106
// speed parameter
107
// SPC SPB SPA
108
//  1   0   1  more slow (05h)     : 42ms   (150%) : 60sample
109
//  1   1   x  slow      (06h,07h) : 34ms   (125%) : 50sample
110
//  x   0   0  normal    (00h,04h) : 25.6ms (100%) : 40sample
111
//  0   0   1  fast      (01h)     : 20.2ms  (75%) : 30sample
112
//  0   1   x  more fast (02h,03h) : 12.2ms  (50%) : 20sample
113
static constexpr std::array<uint8_t, 8> VLM5030_speed_table =
114
{
115
	IP_SIZE_NORMAL,
116
	IP_SIZE_FAST,
117
	IP_SIZE_FASTER,
118
	IP_SIZE_FASTER,
119
	IP_SIZE_NORMAL,
120
	IP_SIZE_SLOWER,
121
	IP_SIZE_SLOW,
122
	IP_SIZE_SLOW
123
};
124
125
// ROM Tables
126
127
// This is the energy lookup table
128
129
// sampled from real chip
130
static constexpr std::array<uint16_t, 0x20> energyTable =
131
{
132
	  0,  2,  4,  6, 10, 12, 14, 18, //  0-7
133
	 22, 26, 30, 34, 38, 44, 48, 54, //  8-15
134
	 62, 68, 76, 84, 94,102,114,124, // 16-23
135
	136,150,164,178,196,214,232,254  // 24-31
136
};
137
138
// This is the pitch lookup table
139
static constexpr std::array<uint8_t, 0x20> pitchTable =
140
{
141
	1,                               // 0     : random mode
142
	22,                              // 1     : start=22
143
	23, 24, 25, 26, 27, 28, 29, 30,  //  2- 9 : 1step
144
	32, 34, 36, 38, 40, 42, 44, 46,  // 10-17 : 2step
145
	50, 54, 58, 62, 66, 70, 74, 78,  // 18-25 : 4step
146
	86, 94, 102,110,118,126          // 26-31 : 8step
147
};
148
149
static constexpr std::array<int16_t, 64> K1_table = {
150
	-24898,  -25672,  -26446,  -27091,  -27736,  -28252,  -28768,  -29155,
151
	-29542,  -29929,  -30316,  -30574,  -30832,  -30961,  -31219,  -31348,
152
	-31606,  -31735,  -31864,  -31864,  -31993,  -32122,  -32122,  -32251,
153
	-32251,  -32380,  -32380,  -32380,  -32509,  -32509,  -32509,  -32509,
154
	 24898,   23995,   22963,   21931,   20770,   19480,   18061,   16642,
155
	 15093,   13416,   11610,    9804,    7998,    6063,    3999,    1935,
156
	     0,   -1935,   -3999,   -6063,   -7998,   -9804,  -11610,  -13416,
157
	-15093,  -16642,  -18061,  -19480,  -20770,  -21931,  -22963,  -23995
158
};
159
static constexpr std::array<int16_t, 32> K2_table = {
160
	     0,   -3096,   -6321,   -9417,  -12513,  -15351,  -18061,  -20770,
161
	-23092,  -25285,  -27220,  -28897,  -30187,  -31348,  -32122,  -32638,
162
	     0,   32638,   32122,   31348,   30187,   28897,   27220,   25285,
163
	 23092,   20770,   18061,   15351,   12513,    9417,    6321,    3096
164
};
165
static constexpr std::array<int16_t, 16> K3_table = {
166
	    0,   -3999,   -8127,  -12255,  -16384,  -20383,  -24511,  -28639,
167
	32638,   28639,   24511,   20383,   16254,   12255,    8127,    3999
168
};
169
static constexpr std::array<int16_t, 8> K5_table = {
170
	0,   -8127,  -16384,  -24511,   32638,   24511,   16254,    8127
171
};
172
173
unsigned VLM5030::getBits(unsigned sBit, unsigned bits) const
174
{
175
	unsigned offset = address + (sBit / 8);
176
	unsigned data = rom[(offset + 0) & address_mask] +
177
	                rom[(offset + 1) & address_mask] * 256;
178
	data >>= (sBit & 7);
179
	data &= (0xFF >> (8 - bits));
180
	return data;
181
}
182
183
// get next frame
184
int VLM5030::parseFrame()
185
{
186
	// remember previous frame
187
	old_energy = new_energy;
188
	old_pitch = new_pitch;
189
	old_k = new_k;
190
	// command byte check
191
	if (uint8_t cmd = rom[address & address_mask];
192
	    cmd & 0x01) {
193
		// extend frame
194
		new_energy = new_pitch = 0;
195
		std::ranges::fill(new_k, 0);
196
		++address;
197
		if (cmd & 0x02) {
198
			// end of speech
199
			return 0;
200
		} else {
201
			// silent frame
202
			int nums = ((cmd >> 2) + 1) * 2;
203
			return nums * FR_SIZE;
204
		}
205
	}
206
	// pitch
207
	new_pitch  = narrow_cast<uint8_t>((pitchTable[getBits(1, 5)] + pitch_offset) & 0xff);
208
	// energy
209
	new_energy = energyTable[getBits(6, 5)];
210
211
	// 10 K's
212
	new_k[9] = K5_table[getBits(11, 3)];
213
	new_k[8] = K5_table[getBits(14, 3)];
214
	new_k[7] = K5_table[getBits(17, 3)];
215
	new_k[6] = K5_table[getBits(20, 3)];
216
	new_k[5] = K5_table[getBits(23, 3)];
217
	new_k[4] = K5_table[getBits(26, 3)];
218
	new_k[3] = K3_table[getBits(29, 4)];
219
	new_k[2] = K3_table[getBits(33, 4)];
220
	new_k[1] = K2_table[getBits(37, 5)];
221
	new_k[0] = K1_table[getBits(42, 6)];
222
223
	address += 6;
224
	return FR_SIZE;
225
}
226
227
// decode and buffering data
228
void VLM5030::generateChannels(std::span<float*> bufs, unsigned num)
229
{
230
	// Single channel device: replace content of bufs[0] (not add to it).
231
	using enum Phase;
232
	if (phase == IDLE) {
233
		bufs[0] = nullptr;
234
		return;
235
	}
236
237
	int buf_count = 0;
238
239
	// running
240
	if (phase == one_of(RUN, STOP)) {
241
		// playing speech
242
		while (num > 0) {
243
			// check new interpolator or new frame
244
			if (sample_count == 0) {
245
				if (phase == STOP) {
246
					phase = END;
247
					sample_count = 1;
248
					goto phase_stop; // continue to end phase
249
				}
250
				sample_count = frame_size;
251
				// interpolator changes
252
				if (interp_count == 0) {
253
					// change to new frame
254
					interp_count = narrow_cast<uint8_t>(parseFrame()); // with change phase
255
					if (interp_count == 0) {
256
						// end mark found
257
						interp_count = FR_SIZE;
258
						sample_count = frame_size; // end -> stop time
259
						phase = STOP;
260
					}
261
					// Set old target as new start of frame
262
					current_energy = old_energy;
263
					current_pitch = old_pitch;
264
					copy_to_range(old_k, current_k); // no assignment because arrays have different type (intentional?)
265
					// is this a zero energy frame?
266
					if (current_energy == 0) {
267
						target_energy = 0;
268
						target_pitch = narrow_cast<uint8_t>(current_pitch);
269
						std::ranges::transform(current_k, target_k.data(), [](int k) { return int16_t(k); }); // type conversion intentional?
270
					} else {
271
						// normal frame
272
						target_energy = new_energy;
273
						target_pitch = new_pitch;
274
						target_k = new_k;
275
					}
276
				}
277
				// next interpolator
278
				// Update values based on step values 25%, 50%, 75%, 100%
279
				interp_count -= interp_step;
280
				// 3,2,1,0 -> 1,2,3,4
281
				int interp_effect = FR_SIZE - (interp_count % FR_SIZE);
282
				current_energy = old_energy + (target_energy - old_energy) * interp_effect / FR_SIZE;
283
				if (old_pitch > 1) {
284
					current_pitch = old_pitch + (target_pitch - old_pitch) * interp_effect / FR_SIZE;
285
				}
286
				for (auto i : xrange(10))
287
					current_k[i] = old_k[i] + (target_k[i] - old_k[i]) * interp_effect / FR_SIZE;
288
			}
289
			// calculate digital filter
290
			int current_val = [&] {
291
				if (old_energy == 0) {
292
					// generate silent samples here
293
					return 0;
294
				} else if (old_pitch <= 1) {
295
					// generate unvoiced samples here
296
					return random_bool() ?  int(current_energy)
297
					                     : -int(current_energy);
298
				} else {
299
					// generate voiced samples here
300
					return (pitch_count == 0) ? int(current_energy) : 0;
301
				}
302
			}();
303
304
			// Lattice filter here
305
			std::array<int, 11> u;
306
			u[10] = current_val;
307
			for (int i = 9; i >= 0; --i) {
308
				u[i] = u[i + 1] - ((current_k[i] * x[i]) / 32768);
309
			}
310
			for (int i = 9; i >= 1; --i) {
311
				x[i] = x[i - 1] + ((current_k[i - 1] * u[i - 1]) / 32768);
312
			}
313
			x[0] = u[0];
314
315
			// clipping, buffering
316
			bufs[0][buf_count] = narrow<float>(std::clamp(u[0], -511, 511));
317
			++buf_count;
318
			--sample_count;
319
			++pitch_count;
320
			if (pitch_count >= current_pitch) {
321
				pitch_count = 0;
322
			}
323
			--num;
324
		}
325
	// return;
326
	}
327
phase_stop:
328
	switch (phase) {
329
	using enum Phase;
330
	case SETUP:
331
		if (sample_count <= num) {
332
			sample_count = 0;
333
			// pin_BSY = true;
334
			phase = WAIT;
335
		} else {
336
			sample_count -= narrow<uint8_t>(num);
337
		}
338
		break;
339
	case END:
340
		if (sample_count <= num) {
341
			sample_count = 0;
342
			pin_BSY = false;
343
			phase = IDLE;
344
		} else {
345
			sample_count -= narrow<uint8_t>(num);
346
		}
347
		break;
348
	default:
349
		break; // nothing
350
	}
351
	// silent buffering
352
	while (num > 0) {
353
		bufs[0][buf_count++] = 0;
354
		--num;
355
	}
356
}
357
358
float VLM5030::getAmplificationFactorImpl() const
359
{
360
	return 1.0f / (1 << 9);
361
}
362
363
// setup parameter option when RST=H
364
void VLM5030::setupParameter(uint8_t param)
365
{
366
	// latch parameter value
367
	parameter = param;
368
369
	// bit 0,1 : 4800bps / 9600bps , interpolator step
370
	if (param & 2) {          // bit 1 = 1 , 9600bps
371
		interp_step = 4;  // 9600bps : no interpolator
372
	} else if (param & 1) {   // bit1 = 0 & bit0 = 1 , 4800bps
373
		interp_step = 2;  // 4800bps : 2 interpolator
374
	} else {                  // bit1 = bit0 = 0 : 2400bps
375
		interp_step = 1;  // 2400bps : 4 interpolator
376
	}
377
378
	// bit 3,4,5 : speed (frame size)
379
	frame_size = VLM5030_speed_table[(param >> 3) & 7];
380
381
	// bit 6,7 : low / high pitch
382
	if (param & 0x80) {        // bit7=1 , high pitch
383
		pitch_offset = -8;
384
	} else if (param & 0x40) { // bit6=1 , low pitch
385
		pitch_offset = 8;
386
	} else {
387
		pitch_offset = 0;
388
	}
389
}
390
391
void VLM5030::reset()
392
{
393
	phase = Phase::RESET;
394
	address = 0;
395
	vcu_addr_h = 0;
396
	pin_BSY = false;
397
398
	old_energy = old_pitch = 0;
399
	new_energy = new_pitch = 0;
400
	current_energy = current_pitch = 0;
401
	target_energy = target_pitch = 0;
402
	std::ranges::fill(old_k, 0);
403
	std::ranges::fill(new_k, 0);
404
	std::ranges::fill(current_k, 0);
405
	std::ranges::fill(target_k, 0);
406
	interp_count = sample_count = pitch_count = 0;
407
	std::ranges::fill(x, 0);
408
	// reset parameters
409
	setupParameter(0x00);
410
}
411
412
// get BSY pin level
413
bool VLM5030::getBSY(EmuTime time) const
414
{
415
	const_cast<VLM5030*>(this)->updateStream(time);
416
	return pin_BSY;
417
}
418
419
// latch control data
420
void VLM5030::writeData(uint8_t data)
421
{
422
	latch_data = data;
423
}
424
425
void VLM5030::writeControl(uint8_t data, EmuTime time)
426
{
427
	updateStream(time);
428
	setRST((data & 0x01) != 0);
429
	setVCU((data & 0x04) != 0);
430
	setST ((data & 0x02) != 0);
431
}
432
433
// set RST pin level : reset / set table address A8-A15
434
void VLM5030::setRST(bool pin)
435
{
436
	if (pin_RST) {
437
		if (!pin) { // H -> L : latch parameters
438
			pin_RST = false;
439
			setupParameter(latch_data);
440
		}
441
	} else {
442
		if (pin) { // L -> H : reset chip
443
			pin_RST = true;
444
			if (pin_BSY) {
445
				reset();
446
			}
447
		}
448
	}
449
}
450
451
// set VCU pin level : ?? unknown
452
void VLM5030::setVCU(bool pin)
453
{
454
	// direct mode / indirect mode
455
	pin_VCU = pin;
456
}
457
458
// set ST pin level  : set table address A0-A7 / start speech
459
void VLM5030::setST(bool pin)
460
{
461
	if (pin_ST == pin) {
462
		// pin level unchanged
463
		return;
464
	}
465
	if (!pin) {
466
		// H -> L
467
		pin_ST = false;
468
		if (pin_VCU) {
469
			// direct access mode & address High
470
			vcu_addr_h = narrow<uint16_t>((latch_data << 8) + 0x01);
471
		} else {
472
			// check access mode
473
			if (vcu_addr_h) {
474
				// direct access mode
475
				address = (vcu_addr_h & 0xff00) + latch_data;
476
				vcu_addr_h = 0;
477
			} else {
478
				// indirect access mode
479
				int table = (latch_data & 0xfe) + ((int(latch_data) & 1) << 8);
480
				address = uint16_t((rom[(table + 0) & address_mask] << 8) |
481
				                   (rom[(table + 1) & address_mask] << 0));
482
			}
483
			// reset process status
484
			sample_count = frame_size;
485
			interp_count = FR_SIZE;
486
			// clear filter
487
			// start after 3 sampling cycle
488
			phase = Phase::RUN;
489
		}
490
	} else {
491
		// L -> H
492
		pin_ST = true;
493
		// setup speech, BSY on after 30ms?
494
		phase = Phase::SETUP;
495
		sample_count = 1; // wait time for busy on
496
		pin_BSY = true;
497
	}
498
}
499
500
501
static XMLElement* getRomConfig(
502
	DeviceConfig& config, const std::string& name, std::string_view romFilename)
503
{
504
	auto& doc = config.getXMLDocument();
505
	auto* voiceROMconfig = doc.allocateElement(doc.allocateString(name));
506
	voiceROMconfig->setFirstAttribute(doc.allocateAttribute("id", "name"));
507
	auto* romElement = voiceROMconfig->setFirstChild(doc.allocateElement("rom"));
508
	romElement->setFirstChild(doc.allocateElement( // load by sha1sum
509
			"sha1", "4f36d139ee4baa7d5980f765de9895570ee05f40"))
510
	         ->setNextSibling(doc.allocateElement( // load by predefined filename in software rom's dir
511
			"filename",
512
			doc.allocateString(tmpStrCat(FileOperations::stripExtension(romFilename), "_voice.rom"))))
513
		 ->setNextSibling(doc.allocateElement( // or hardcoded filename in ditto dir
514
			"filename", "keyboardmaster/voice.rom"));
515
	return voiceROMconfig;
516
}
517
518
static constexpr auto INPUT_RATE = unsigned(cstd::round(3579545 / 440.0));
519
520
VLM5030::VLM5030(const std::string& name_, static_string_view desc,
521
                 std::string_view romFilename, DeviceConfig& config)
522
	: ResampledSoundDevice(config.getMotherBoard(), name_, desc, 1, INPUT_RATE, false)
523
	, config2(config, *getRomConfig(config, name_, romFilename))
524
	, rom(name_ + " ROM", "rom", config2)
525
{
526
	reset();
527
	phase = Phase::IDLE;
528
529
	assert(rom.size() != 0);
530
	address_mask = narrow<unsigned>(rom.size() - 1);
531
532
	registerSound(config);
533
}
534
535
VLM5030::~VLM5030()
536
{
537
	unregisterSound();
538
}
539
540
static constexpr auto phaseInfo = std::to_array<enum_string<VLM5030::Phase>>({
541
	{ "RESET", VLM5030::Phase::RESET },
542
	{ "IDLE",  VLM5030::Phase::IDLE  },
543
	{ "SETUP", VLM5030::Phase::SETUP },
544
	{ "WAIT",  VLM5030::Phase::WAIT  },
545
	{ "RUN",   VLM5030::Phase::RUN   },
546
	{ "STOP",  VLM5030::Phase::STOP  },
547
	{ "END",   VLM5030::Phase::END   },
548
});
549
SERIALIZE_ENUM(VLM5030::Phase, phaseInfo);
550
551
// version 1: initial version
552
// version 2: serialize 'phase' as an enum instead of uint8_t
553
template<typename Archive>
554
void VLM5030::serialize(Archive& ar, unsigned version)
555
{
556
	ar.serialize("address_mask",   address_mask,
557
	             "frame_size",     frame_size,
558
	             "pitch_offset",   pitch_offset,
559
	             "current_energy", current_energy,
560
	             "current_pitch",  current_pitch,
561
	             "current_k",      current_k,
562
	             "x",              x,
563
	             "address",        address,
564
	             "vcu_addr_h",     vcu_addr_h,
565
	             "old_k",          old_k,
566
	             "new_k",          new_k,
567
	             "target_k",       target_k,
568
	             "old_energy",     old_energy,
569
	             "new_energy",     new_energy,
570
	             "target_energy",  target_energy,
571
	             "old_pitch",      old_pitch,
572
	             "new_pitch",      new_pitch,
573
	             "target_pitch",   target_pitch,
574
	             "interp_step",    interp_step,
575
	             "interp_count",   interp_count,
576
	             "sample_count",   sample_count,
577
	             "pitch_count",    pitch_count,
578
	             "latch_data",     latch_data,
579
	             "parameter",      parameter,
580
	             "pin_BSY",        pin_BSY,
581
	             "pin_ST",         pin_ST,
582
	             "pin_VCU",        pin_VCU,
583
	             "pin_RST",        pin_RST);
584
	if (ar.versionAtLeast(version, 2)) {
585
		ar.serialize("phase", phase);
586
	} else {
587
		assert(Archive::IS_LOADER);
588
		int tmp = 0; // dummy init to avoid warning
589
		ar.serialize("phase", tmp);
590
		switch (tmp) {
591
			using enum Phase;
592
			case 0:  phase = RESET; break;
593
			case 1:  phase = IDLE;  break;
594
			case 2:  phase = SETUP; break;
595
			case 3:  phase = WAIT;  break;
596
			case 4:  phase = RUN;   break;
597
			case 5:  phase = STOP;  break;
598
			default: phase = END;   break;
599
		}
600
	}
601
}
602
603
INSTANTIATE_SERIALIZE_METHODS(VLM5030);
604
605
} // namespace openmsx