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