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