openMSX
VLM5030.cc
Go to the documentation of this file.
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=noize , 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 == 440systemclock(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-..: vokume 0 : silent
64 
65  ---------- digial 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 #include "DeviceConfig.hh"
79 #include "XMLElement.hh"
80 #include "FileOperations.hh"
81 #include "serialize.hh"
82 #include "random.hh"
83 #include <cstring>
84 #include <cstdint>
85 
86 namespace openmsx {
87 
88 
89 // interpolator per frame
90 static const int FR_SIZE = 4;
91 // samples per interpolator
92 static const int IP_SIZE_SLOWER = 240 / FR_SIZE;
93 static const int IP_SIZE_SLOW = 200 / FR_SIZE;
94 static const int IP_SIZE_NORMAL = 160 / FR_SIZE;
95 static const int IP_SIZE_FAST = 120 / FR_SIZE;
96 static const int IP_SIZE_FASTER = 80 / FR_SIZE;
97 
98 // phase value
99 enum {
107 };
108 
109 // speed parameter
110 // SPC SPB SPA
111 // 1 0 1 more slow (05h) : 42ms (150%) : 60sample
112 // 1 1 x slow (06h,07h) : 34ms (125%) : 50sample
113 // x 0 0 normal (00h,04h) : 25.6ms (100%) : 40samplme
114 // 0 0 1 fast (01h) : 20.2ms (75%) : 30sample
115 // 0 1 x more fast (02h,03h) : 12.2ms (50%) : 20sample
116 static const int VLM5030_speed_table[8] =
117 {
118  IP_SIZE_NORMAL,
119  IP_SIZE_FAST,
120  IP_SIZE_FASTER,
121  IP_SIZE_FASTER,
122  IP_SIZE_NORMAL,
123  IP_SIZE_SLOWER,
124  IP_SIZE_SLOW,
125  IP_SIZE_SLOW
126 };
127 
128 // ROM Tables
129 
130 // This is the energy lookup table
131 
132 // sampled from real chip
133 static word energytable[0x20] =
134 {
135  0, 2, 4, 6, 10, 12, 14, 18, // 0-7
136  22, 26, 30, 34, 38, 44, 48, 54, // 8-15
137  62, 68, 76, 84, 94,102,114,124, // 16-23
138  136,150,164,178,196,214,232,254 // 24-31
139 };
140 
141 // This is the pitch lookup table
142 static const byte pitchtable [0x20] =
143 {
144  1, // 0 : random mode
145  22, // 1 : start=22
146  23, 24, 25, 26, 27, 28, 29, 30, // 2- 9 : 1step
147  32, 34, 36, 38, 40, 42, 44, 46, // 10-17 : 2step
148  50, 54, 58, 62, 66, 70, 74, 78, // 18-25 : 4step
149  86, 94, 102,110,118,126 // 26-31 : 8step
150 };
151 
152 static const int16_t K1_table[] = {
153  -24898, -25672, -26446, -27091, -27736, -28252, -28768, -29155,
154  -29542, -29929, -30316, -30574, -30832, -30961, -31219, -31348,
155  -31606, -31735, -31864, -31864, -31993, -32122, -32122, -32251,
156  -32251, -32380, -32380, -32380, -32509, -32509, -32509, -32509,
157  24898, 23995, 22963, 21931, 20770, 19480, 18061, 16642,
158  15093, 13416, 11610, 9804, 7998, 6063, 3999, 1935,
159  0, -1935, -3999, -6063, -7998, -9804, -11610, -13416,
160  -15093, -16642, -18061, -19480, -20770, -21931, -22963, -23995
161 };
162 static const int16_t K2_table[] = {
163  0, -3096, -6321, -9417, -12513, -15351, -18061, -20770,
164  -23092, -25285, -27220, -28897, -30187, -31348, -32122, -32638,
165  0, 32638, 32122, 31348, 30187, 28897, 27220, 25285,
166  23092, 20770, 18061, 15351, 12513, 9417, 6321, 3096
167 };
168 static const int16_t K3_table[] = {
169  0, -3999, -8127, -12255, -16384, -20383, -24511, -28639,
170  32638, 28639, 24511, 20383, 16254, 12255, 8127, 3999
171 };
172 static const int16_t K5_table[] = {
173  0, -8127, -16384, -24511, 32638, 24511, 16254, 8127
174 };
175 
176 int VLM5030::getBits(unsigned sbit, unsigned bits)
177 {
178  unsigned offset = address + (sbit / 8);
179  unsigned data = rom[(offset + 0) & address_mask] +
180  rom[(offset + 1) & address_mask] * 256;
181  data >>= (sbit & 7);
182  data &= (0xFF >> (8 - bits));
183  return data;
184 }
185 
186 // get next frame
187 int VLM5030::parseFrame()
188 {
189  // remember previous frame
190  old_energy = new_energy;
191  old_pitch = new_pitch;
192  for (int i = 0; i <= 9; ++i) {
193  old_k[i] = new_k[i];
194  }
195  // command byte check
196  byte cmd = rom[address & address_mask];
197  if (cmd & 0x01) {
198  // extend frame
199  new_energy = new_pitch = 0;
200  for (int i = 0; i <= 9; ++i) {
201  new_k[i] = 0;
202  }
203  ++address;
204  if (cmd & 0x02) {
205  // end of speech
206  return 0;
207  } else {
208  // silent frame
209  int nums = ((cmd >> 2) + 1) * 2;
210  return nums * FR_SIZE;
211  }
212  }
213  // pitch
214  new_pitch = (pitchtable[getBits(1, 5)] + pitch_offset) & 0xff;
215  // energy
216  new_energy = energytable[getBits(6, 5)];
217 
218  // 10 K's
219  new_k[9] = K5_table[getBits(11, 3)];
220  new_k[8] = K5_table[getBits(14, 3)];
221  new_k[7] = K5_table[getBits(17, 3)];
222  new_k[6] = K5_table[getBits(20, 3)];
223  new_k[5] = K5_table[getBits(23, 3)];
224  new_k[4] = K5_table[getBits(26, 3)];
225  new_k[3] = K3_table[getBits(29, 4)];
226  new_k[2] = K3_table[getBits(33, 4)];
227  new_k[1] = K2_table[getBits(37, 5)];
228  new_k[0] = K1_table[getBits(42, 6)];
229 
230  address += 6;
231  return FR_SIZE;
232 }
233 
234 // decode and buffering data
235 void VLM5030::generateChannels(int** bufs, unsigned length)
236 {
237  // Single channel device: replace content of bufs[0] (not add to it).
238  if (phase == PH_IDLE) {
239  bufs[0] = nullptr;
240  return;
241  }
242 
243  int buf_count = 0;
244 
245  // running
246  if (phase == PH_RUN || phase == PH_STOP) {
247  // playing speech
248  while (length > 0) {
249  int current_val;
250  // check new interpolator or new frame
251  if (sample_count == 0) {
252  if (phase == PH_STOP) {
253  phase = PH_END;
254  sample_count = 1;
255  goto phase_stop; // continue to end phase
256  }
257  sample_count = frame_size;
258  // interpolator changes
259  if (interp_count == 0) {
260  // change to new frame
261  interp_count = parseFrame(); // with change phase
262  if (interp_count == 0 ) {
263  // end mark found
264  interp_count = FR_SIZE;
265  sample_count = frame_size; // end -> stop time
266  phase = PH_STOP;
267  }
268  // Set old target as new start of frame
269  current_energy = old_energy;
270  current_pitch = old_pitch;
271  for (int i = 0; i <= 9; ++i) {
272  current_k[i] = old_k[i];
273  }
274  // is this a zero energy frame?
275  if (current_energy == 0) {
276  target_energy = 0;
277  target_pitch = current_pitch;
278  for (int i = 0; i <= 9; ++i) {
279  target_k[i] = current_k[i];
280  }
281  } else {
282  // normal frame
283  target_energy = new_energy;
284  target_pitch = new_pitch;
285  for (int i = 0; i <= 9; ++i) {
286  target_k[i] = new_k[i];
287  }
288  }
289  }
290  // next interpolator
291  // Update values based on step values 25%, 50%, 75%, 100%
292  interp_count -= interp_step;
293  // 3,2,1,0 -> 1,2,3,4
294  int interp_effect = FR_SIZE - (interp_count % FR_SIZE);
295  current_energy = old_energy + (target_energy - old_energy) * interp_effect / FR_SIZE;
296  if (old_pitch > 1) {
297  current_pitch = old_pitch + (target_pitch - old_pitch) * interp_effect / FR_SIZE;
298  }
299  for (int i = 0; i <= 9; ++i)
300  current_k[i] = old_k[i] + (target_k[i] - old_k[i]) * interp_effect / FR_SIZE;
301  }
302  // calcrate digital filter
303  if (old_energy == 0) {
304  // generate silent samples here
305  current_val = 0x00;
306  } else if (old_pitch <= 1) {
307  // generate unvoiced samples here
308  current_val = random_bool() ? int(current_energy)
309  : -int(current_energy);
310  } else {
311  // generate voiced samples here
312  current_val = (pitch_count == 0) ? current_energy : 0;
313  }
314 
315  // Lattice filter here
316  int u[11];
317  u[10] = current_val;
318  for (int i = 9; i >= 0; --i) {
319  u[i] = u[i + 1] - ((current_k[i] * x[i]) / 32768);
320  }
321  for (int i = 9; i >= 1; --i) {
322  x[i] = x[i - 1] + ((current_k[i - 1] * u[i - 1]) / 32768);
323  }
324  x[0] = u[0];
325 
326  // clipping, buffering
327  if (u[0] > 511) {
328  bufs[0][buf_count] = 511 << 6;
329  } else if (u[0] < -511) {
330  bufs[0][buf_count] = -511 << 6;
331  } else {
332  bufs[0][buf_count] = (u[0] << 6);
333  }
334  ++buf_count;
335  --sample_count;
336  ++pitch_count;
337  if (pitch_count >= current_pitch) {
338  pitch_count = 0;
339  }
340  --length;
341  }
342  // return;
343  }
344 phase_stop:
345  switch (phase) {
346  case PH_SETUP:
347  if (sample_count <= length) {
348  sample_count = 0;
349  // pin_BSY = true;
350  phase = PH_WAIT;
351  } else {
352  sample_count -= length;
353  }
354  break;
355  case PH_END:
356  if (sample_count <= length) {
357  sample_count = 0;
358  pin_BSY = false;
359  phase = PH_IDLE;
360  } else {
361  sample_count -= length;
362  }
363  }
364  // silent buffering
365  while (length > 0) {
366  bufs[0][buf_count++] = 0;
367  --length;
368  }
369 }
370 
371 // setup parameteroption when RST=H
372 void VLM5030::setupParameter(byte param)
373 {
374  // latch parameter value
375  parameter = param;
376 
377  // bit 0,1 : 4800bps / 9600bps , interporator step
378  if (param & 2) { // bit 1 = 1 , 9600bps
379  interp_step = 4; // 9600bps : no interporator
380  } else if (param & 1) { // bit1 = 0 & bit0 = 1 , 4800bps
381  interp_step = 2; // 4800bps : 2 interporator
382  } else { // bit1 = bit0 = 0 : 2400bps
383  interp_step = 1; // 2400bps : 4 interporator
384  }
385 
386  // bit 3,4,5 : speed (frame size)
387  frame_size = VLM5030_speed_table[(param >> 3) & 7];
388 
389  // bit 6,7 : low / high pitch
390  if (param & 0x80) { // bit7=1 , high pitch
391  pitch_offset = -8;
392  } else if (param & 0x40) { // bit6=1 , low pitch
393  pitch_offset = 8;
394  } else {
395  pitch_offset = 0;
396  }
397 }
398 
400 {
401  phase = PH_RESET;
402  address = 0;
403  vcu_addr_h = 0;
404  pin_BSY = false;
405 
406  old_energy = old_pitch = 0;
407  new_energy = new_pitch = 0;
408  current_energy = current_pitch = 0;
409  target_energy = target_pitch = 0;
410  memset(old_k, 0, sizeof(old_k));
411  memset(new_k, 0, sizeof(new_k));
412  memset(current_k, 0, sizeof(current_k));
413  memset(target_k, 0, sizeof(target_k));
414  interp_count = sample_count = pitch_count = 0;
415  memset(x, 0, sizeof(x));
416  // reset parameters
417  setupParameter(0x00);
418 }
419 
420 // get BSY pin level
422 {
423  const_cast<VLM5030*>(this)->updateStream(time);
424  return pin_BSY;
425 }
426 
427 // latch control data
429 {
430  latch_data = data;
431 }
432 
434 {
435  updateStream(time);
436  setRST((data & 0x01) != 0);
437  setVCU((data & 0x04) != 0);
438  setST ((data & 0x02) != 0);
439 }
440 
441 // set RST pin level : reset / set table address A8-A15
442 void VLM5030::setRST(bool pin)
443 {
444  if (pin_RST) {
445  if (!pin) { // H -> L : latch parameters
446  pin_RST = false;
447  setupParameter(latch_data);
448  }
449  } else {
450  if (pin) { // L -> H : reset chip
451  pin_RST = true;
452  if (pin_BSY) {
453  reset();
454  }
455  }
456  }
457 }
458 
459 // set VCU pin level : ?? unknown
460 void VLM5030::setVCU(bool pin)
461 {
462  // direct mode / indirect mode
463  pin_VCU = pin;
464 }
465 
466 // set ST pin level : set table address A0-A7 / start speech
467 void VLM5030::setST(bool pin)
468 {
469  if (pin_ST == pin) {
470  // pin level unchanged
471  return;
472  }
473  if (!pin) {
474  // H -> L
475  pin_ST = false;
476  if (pin_VCU) {
477  // direct access mode & address High
478  vcu_addr_h = (int(latch_data) << 8) + 0x01;
479  } else {
480  // check access mode
481  if (vcu_addr_h) {
482  // direct access mode
483  address = (vcu_addr_h & 0xff00) + latch_data;
484  vcu_addr_h = 0;
485  } else {
486  // indirect access mode
487  int table = (latch_data & 0xfe) + ((int(latch_data) & 1) << 8);
488  address = ((rom[(table + 0) & address_mask]) << 8) |
489  rom[(table + 1) & address_mask];
490  }
491  // reset process status
492  sample_count = frame_size;
493  interp_count = FR_SIZE;
494  // clear filter
495  // start after 3 sampling cycle
496  phase = PH_RUN;
497  }
498  } else {
499  // L -> H
500  pin_ST = true;
501  // setup speech, BSY on after 30ms?
502  phase = PH_SETUP;
503  sample_count = 1; // wait time for busy on
504  pin_BSY = true;
505  }
506 }
507 
508 
509 static XMLElement getRomConfig(const std::string& name, const std::string& romFilename)
510 {
511  XMLElement voiceROMconfig(name);
512  voiceROMconfig.addAttribute("id", "name");
513  auto& romElement = voiceROMconfig.addChild("rom");
514  romElement.addChild( // load by sha1sum
515  "sha1", "4f36d139ee4baa7d5980f765de9895570ee05f40");
516  romElement.addChild( // load by predefined filename in software rom's dir
517  "filename", FileOperations::stripExtension(romFilename) + "_voice.rom");
518  romElement.addChild( // or hardcoded filename in ditto dir
519  "filename", "keyboardmaster/voice.rom");
520  return voiceROMconfig;
521 }
522 
523 VLM5030::VLM5030(const std::string& name, const std::string& desc,
524  const std::string& romFilename, const DeviceConfig& config)
525  : ResampledSoundDevice(config.getMotherBoard(), name, desc, 1)
526  , rom(name + " ROM", "rom", DeviceConfig(config, getRomConfig(name, romFilename)))
527 {
528  // reset input pins
529  pin_RST = pin_ST = pin_VCU = false;
530  latch_data = 0;
531 
532  reset();
533  phase = PH_IDLE;
534 
535  address_mask = rom.getSize() - 1;
536 
537  const int CLOCK_FREQ = 3579545;
538  float input = CLOCK_FREQ / 440.0f;
539  setInputRate(int(input + 0.5f));
540 
541  registerSound(config);
542 }
543 
545 {
546  unregisterSound();
547 }
548 
549 template<typename Archive>
550 void VLM5030::serialize(Archive& ar, unsigned /*version*/)
551 {
552  ar.serialize("address_mask", address_mask);
553  ar.serialize("frame_size", frame_size);
554  ar.serialize("pitch_offset", pitch_offset);
555  ar.serialize("current_energy", current_energy);
556  ar.serialize("current_pitch", current_pitch);
557  ar.serialize("current_k", current_k);
558  ar.serialize("x", x);
559  ar.serialize("address", address);
560  ar.serialize("vcu_addr_h", vcu_addr_h);
561  ar.serialize("old_k", old_k);
562  ar.serialize("new_k", new_k);
563  ar.serialize("target_k", target_k);
564  ar.serialize("old_energy", old_energy);
565  ar.serialize("new_energy", new_energy);
566  ar.serialize("target_energy", target_energy);
567  ar.serialize("old_pitch", old_pitch);
568  ar.serialize("new_pitch", new_pitch);
569  ar.serialize("target_pitch", target_pitch);
570  ar.serialize("interp_step", interp_step);
571  ar.serialize("interp_count", interp_count);
572  ar.serialize("sample_count", sample_count);
573  ar.serialize("pitch_count", pitch_count);
574  ar.serialize("latch_data", latch_data);
575  ar.serialize("parameter", parameter);
576  ar.serialize("phase", phase);
577  ar.serialize("pin_BSY", pin_BSY);
578  ar.serialize("pin_ST", pin_ST);
579  ar.serialize("pin_VCU", pin_VCU);
580  ar.serialize("pin_RST", pin_RST);
581 }
582 
584 
585 } // namespace openmsx
T length(const vecN< N, T > &x)
Definition: gl_vec.hh:322
void serialize(Archive &ar, unsigned version)
Definition: VLM5030.cc:550
void unregisterSound()
Unregisters this sound device with the Mixer.
Definition: SoundDevice.cc:120
void writeControl(byte data, EmuTime::param time)
set RST / VCU / ST pins
Definition: VLM5030.cc:433
void writeData(byte data)
latch control data
Definition: VLM5030.cc:428
void updateStream(EmuTime::param time)
Definition: SoundDevice.cc:125
void setInputRate(unsigned sampleRate)
Definition: SoundDevice.hh:79
bool getBSY(EmuTime::param time) const
get BSY pin level
Definition: VLM5030.cc:421
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
unsigned char byte
8 bit unsigned integer
Definition: openmsx.hh:25
bool random_bool()
Return a random boolean value.
Definition: random.hh:24
string_ref stripExtension(string_ref path)
Returns the path without extension.
VLM5030(const std::string &name, const std::string &desc, const std::string &romFilename, const DeviceConfig &config)
Definition: VLM5030.cc:523
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:802
uint8_t * data()
unsigned getSize() const
Definition: Rom.hh:32
void registerSound(const DeviceConfig &config)
Registers this sound device with the Mixer.
Definition: SoundDevice.cc:78
unsigned short word
16 bit unsigned integer
Definition: openmsx.hh:28