openMSX
Y8950Adpcm.cc
Go to the documentation of this file.
1 // The actual sample playing part is duplicated for the 'emu' domain and the
2 // 'audio' domain. The emu part is responsible for cycle accurate sample
3 // readback (see peekReg() register 0x13 and 0x14) and for cycle accurate
4 // status register updates (the status bits related to playback, e.g.
5 // end-of-sample). The audio part is responsible for the actual sound
6 // generation. This split up allows for the two parts to be out-of-sync. So for
7 // example when emulation is running faster or slower than 100% realtime speed,
8 // we both get cycle accurate emulation behaviour and still sound generation at
9 // 100% realtime speed (which is most of the time better for sound quality).
10 
11 #include "Y8950Adpcm.hh"
12 #include "Y8950.hh"
13 #include "Clock.hh"
14 #include "DeviceConfig.hh"
15 #include "MSXMotherBoard.hh"
16 #include "Math.hh"
17 #include "serialize.hh"
18 
19 namespace openmsx {
20 
21 // Bitmask for register 0x07
22 constexpr int R07_RESET = 0x01;
23 constexpr int R07_SP_OFF = 0x08;
24 constexpr int R07_REPEAT = 0x10;
25 constexpr int R07_MEMORY_DATA = 0x20;
26 constexpr int R07_REC = 0x40;
27 constexpr int R07_START = 0x80;
28 constexpr int R07_MODE = 0xE0;
29 
30 // Bitmask for register 0x08
31 constexpr int R08_ROM = 0x01;
32 constexpr int R08_64K = 0x02;
33 constexpr int R08_DA_AD = 0x04;
34 constexpr int R08_SAMPL = 0x08;
35 constexpr int R08_NOTE_SET = 0x40;
36 constexpr int R08_CSM = 0x80;
37 
38 constexpr int DMAX = 0x6000;
39 constexpr int DMIN = 0x7F;
40 constexpr int DDEF = 0x7F;
41 
42 constexpr int STEP_BITS = 16;
43 constexpr int STEP_MASK = (1 << STEP_BITS) -1;
44 
45 
46 Y8950Adpcm::Y8950Adpcm(Y8950& y8950_, const DeviceConfig& config,
47  const std::string& name, unsigned sampleRam)
48  : Schedulable(config.getScheduler())
49  , y8950(y8950_)
50  , ram(config, name + " RAM", "Y8950 sample RAM", sampleRam)
51  , clock(config.getMotherBoard().getCurrentTime())
52  , volume(0)
53 {
54  clearRam();
55 }
56 
58 {
59  ram.clear(0xFF);
60 }
61 
62 void Y8950Adpcm::reset(EmuTime::param time)
63 {
65 
66  clock.reset(time);
67 
68  startAddr = 0;
69  stopAddr = 7;
70  delta = 0;
71  addrMask = (1 << 18) - 1;
72  reg7 = 0;
73  reg15 = 0;
74  readDelay = 0;
75  romBank = false;
76  writeReg(0x12, 255, time); // volume
77 
78  restart(emu);
79  restart(aud);
80 
82 }
83 
84 bool Y8950Adpcm::isPlaying() const
85 {
86  return (reg7 & 0xC0) == 0x80;
87 }
88 bool Y8950Adpcm::isMuted() const
89 {
90  return !isPlaying() || (reg7 & R07_SP_OFF);
91 }
92 
93 void Y8950Adpcm::restart(PlayData& pd)
94 {
95  pd.memPntr = startAddr;
96  pd.nowStep = (1 << STEP_BITS) - delta;
97  pd.out = 0;
98  pd.output = 0;
99  pd.diff = DDEF;
100  pd.nextLeveling = 0;
101  pd.sampleStep = 0;
102  pd.adpcm_data = 0; // dummy, avoid UMR in serialize
103 }
104 
105 void Y8950Adpcm::sync(EmuTime::param time)
106 {
107  if (isPlaying()) { // optimization, also correct without this test
108  unsigned ticks = clock.getTicksTill(time);
109  for (unsigned i = 0; isPlaying() && (i < ticks); ++i) {
110  calcSample(true); // ignore result
111  }
112  }
113  clock.advance(time);
114 }
115 
116 void Y8950Adpcm::schedule()
117 {
118  assert(isPlaying());
119  if ((stopAddr > startAddr) && (delta != 0)) {
120  // TODO possible optimization, no need to set sync points if
121  // the corresponding bit is masked in the interupt enable
122  // register
123  if (reg7 & R07_MEMORY_DATA) {
124  // we already did a sync(time), so clock is up-to-date
126  uint64_t samples = stopAddr - emu.memPntr + 1;
127  uint64_t length = (samples << STEP_BITS) +
128  ((1 << STEP_BITS) - emu.nowStep) +
129  (delta - 1);
130  stop += unsigned(length / delta);
131  setSyncPoint(stop.getTime());
132  } else {
133  // TODO we should also set a syncpoint in this case
134  // because this mode sets the STATUS_BUF_RDY bit
135  // which also triggers an IRQ
136  }
137  }
138 }
139 
140 void Y8950Adpcm::executeUntil(EmuTime::param time)
141 {
142  assert(isPlaying());
143  sync(time); // should set STATUS_EOS
144  assert(y8950.peekRawStatus() & Y8950::STATUS_EOS);
145  if (isPlaying() && (reg7 & R07_REPEAT)) {
146  schedule();
147  }
148 }
149 
150 void Y8950Adpcm::writeReg(byte rg, byte data, EmuTime::param time)
151 {
152  sync(time); // TODO only when needed
153  switch (rg) {
154  case 0x07: // START/REC/MEM DATA/REPEAT/SP-OFF/-/-/RESET
155  reg7 = data;
156  if (reg7 & R07_START) {
158  } else {
160  }
161  if (reg7 & R07_RESET) {
162  reg7 = 0;
163  }
164  if (reg7 & R07_START) {
165  // start ADPCM
166  restart(emu);
167  restart(aud);
168  }
169  if (reg7 & R07_MEMORY_DATA) {
170  // access external memory?
171  emu.memPntr = startAddr;
172  aud.memPntr = startAddr;
173  readDelay = 2; // two dummy reads
174  if ((reg7 & 0xA0) == 0x20) {
175  // Memory read or write
177  }
178  } else {
179  // access via CPU
180  emu.memPntr = 0;
181  aud.memPntr = 0;
182  }
183  removeSyncPoint();
184  if (isPlaying()) {
185  schedule();
186  }
187  break;
188 
189  case 0x08: // CSM/KEY BOARD SPLIT/-/-/SAMPLE/DA AD/64K/ROM
190  romBank = data & R08_ROM;
191  addrMask = data & R08_64K ? (1 << 16) - 1 : (1 << 18) - 1;
192  break;
193 
194  case 0x09: // START ADDRESS (L)
195  startAddr = (startAddr & 0x7F807) | (data << 3);
196  break;
197  case 0x0A: // START ADDRESS (H)
198  startAddr = (startAddr & 0x007FF) | (data << 11);
199  break;
200 
201  case 0x0B: // STOP ADDRESS (L)
202  stopAddr = (stopAddr & 0x7F807) | (data << 3);
203  if (isPlaying()) {
204  removeSyncPoint();
205  schedule();
206  }
207  break;
208  case 0x0C: // STOP ADDRESS (H)
209  stopAddr = (stopAddr & 0x007FF) | (data << 11);
210  if (isPlaying()) {
211  removeSyncPoint();
212  schedule();
213  }
214  break;
215 
216  case 0x0F: // ADPCM-DATA
217  writeData(data);
218  break;
219 
220  case 0x10: // DELTA-N (L)
221  delta = (delta & 0xFF00) | data;
222  volumeWStep = (volume * delta) >> STEP_BITS;
223  if (isPlaying()) {
224  removeSyncPoint();
225  schedule();
226  }
227  break;
228  case 0x11: // DELTA-N (H)
229  delta = (delta & 0x00FF) | (data << 8);
230  volumeWStep = (volume * delta) >> STEP_BITS;
231  if (isPlaying()) {
232  removeSyncPoint();
233  schedule();
234  }
235  break;
236 
237  case 0x12: { // ENVELOP CONTROL
238  volume = data;
239  volumeWStep = (volume * delta) >> STEP_BITS;
240  break;
241  }
242  case 0x0D: // PRESCALE (L)
243  case 0x0E: // PRESCALE (H)
244  case 0x15: // DAC-DATA (bit9-2)
245  case 0x16: // (bit1-0)
246  case 0x17: // (exponent)
247  case 0x1A: // PCM-DATA
248  // not implemented
249  break;
250  }
251 }
252 
253 void Y8950Adpcm::writeData(byte data)
254 {
255  reg15 = data;
256  if ((reg7 & R07_MODE) == 0x60) {
257  // external memory write
258  assert(!isPlaying()); // no need to update the 'aud' data
259  if (readDelay) {
260  emu.memPntr = startAddr;
261  readDelay = 0;
262  }
263  if (emu.memPntr <= stopAddr) {
264  writeMemory(emu.memPntr, data);
265  emu.memPntr += 2; // two nibbles at a time
266 
267  // reset BRDY bit in status register,
268  // which means we are processing the write
270 
271  // setup a timer that will callback us in 10
272  // master clock cycles for Y8950. In the
273  // callback set the BRDY flag to 1 , which
274  // means we have written the data. For now, we
275  // don't really do this; we simply reset and
276  // set the flag in zero time, so that the IRQ
277  // will work.
278 
279  if (emu.memPntr <= stopAddr) {
280  // there's more to transfer: set BRDY
282  } else {
283  // we just received the last byte: set EOS
285  // Eugeny tested that pointer wraps when
286  // continue writing after EOS
287  emu.memPntr = startAddr;
288  }
289  }
290 
291  } else if ((reg7 & R07_MODE) == 0x80) {
292  // ADPCM synthesis from CPU
293 
294  // Reset BRDY bit in status register, which means we
295  // are full of data
297  }
298 }
299 
300 byte Y8950Adpcm::readReg(byte rg, EmuTime::param time)
301 {
302  sync(time); // TODO only when needed
303  byte result = (rg == 0x0F)
304  ? readData() // ADPCM-DATA
305  : peekReg(rg); // other
306  return result;
307 }
308 
309 byte Y8950Adpcm::peekReg(byte rg, EmuTime::param time) const
310 {
311  const_cast<Y8950Adpcm*>(this)->sync(time); // TODO only when needed
312  return peekReg(rg);
313 }
314 
315 byte Y8950Adpcm::peekReg(byte rg) const
316 {
317  switch (rg) {
318  case 0x0F: // ADPCM-DATA
319  return peekData();
320  case 0x13:
321  // TODO check: is this before or after
322  // volume is applied
323  // filtering is performed
324  return (emu.output >> 8) & 0xFF;
325  case 0x14:
326  return emu.output >> 16;
327  default:
328  return 255;
329  }
330 }
331 
333 {
334  // If the BUF_RDY mask is cleared (e.g. by writing the value 0x80 to
335  // register R#4). Reading the status register still has the BUF_RDY
336  // bit set. Without this behavior demos like 'NOP Unknown reality'
337  // hang when testing the amount of sample ram or when uploading data
338  // to the sample ram.
339  //
340  // Before this code was added, those demos also worked but only
341  // because we had a hack that always kept bit BUF_RDY set.
342  //
343  // When the ADPCM unit is not performing any function (e.g. after a
344  // reset), the BUF_RDY bit should still be set. The AUDIO detection
345  // routine in 'MSX-Audio BIOS v1.3' depends on this. See
346  // [3533002] Y8950 not being detected by MSX-Audio v1.3
347  // https://sourceforge.net/tracker/?func=detail&aid=3533002&group_id=38274&atid=421861
348  // TODO I've implemented this as '(reg7 & R07_MODE) == 0', is this
349  // correct/complete?
350  if (((reg7 & R07_MODE & ~R07_REC) == R07_MEMORY_DATA) ||
351  ((reg7 & R07_MODE) == 0)){
352  // transfer to or from sample ram, or no function
354  }
355 }
356 
357 byte Y8950Adpcm::readData()
358 {
359  if ((reg7 & R07_MODE) == R07_MEMORY_DATA) {
360  // external memory read
361  assert(!isPlaying()); // no need to update the 'aud' data
362  if (readDelay) {
363  emu.memPntr = startAddr;
364  }
365  }
366  byte result = peekData();
367  if ((reg7 & R07_MODE) == R07_MEMORY_DATA) {
368  assert(!isPlaying()); // no need to update the 'aud' data
369  if (readDelay) {
370  // two dummy reads
371  --readDelay;
373  } else if (emu.memPntr > stopAddr) {
374  // set EOS bit in status register
376  } else {
377  emu.memPntr += 2; // two nibbles at a time
378 
379  // reset BRDY bit in status register, which means we
380  // are reading the memory now
382 
383  // setup a timer that will callback us in 10 master
384  // clock cycles for Y8950. In the callback set the BRDY
385  // flag to 1, which means we have another data ready.
386  // For now, we don't really do this; we simply reset and
387  // set the flag in zero time, so that the IRQ will work.
388 
389  // set BRDY bit in status register
391  }
392  }
393  return result;
394 }
395 
396 byte Y8950Adpcm::peekData() const
397 {
398  if ((reg7 & R07_MODE) == R07_MEMORY_DATA) {
399  // external memory read
400  assert(!isPlaying()); // no need to update the 'aud' data
401  if (readDelay) {
402  return reg15;
403  } else if (emu.memPntr > stopAddr) {
404  return 0;
405  } else {
406  return readMemory(emu.memPntr);
407  }
408  } else {
409  return 0; // TODO check
410  }
411 }
412 
413 void Y8950Adpcm::writeMemory(unsigned memPntr, byte value)
414 {
415  unsigned addr = (memPntr / 2) & addrMask;
416  if ((addr < ram.getSize()) && !romBank) {
417  ram.write(addr, value);
418  }
419 }
420 byte Y8950Adpcm::readMemory(unsigned memPntr) const
421 {
422  unsigned addr = (memPntr / 2) & addrMask;
423  if (romBank || (addr >= ram.getSize())) {
424  return 0; // checked on a real machine
425  } else {
426  return ram[addr];
427  }
428 }
429 
431 {
432  // called by audio thread
433  if (!isPlaying()) return 0;
434  int output = calcSample(false);
435  return (reg7 & R07_SP_OFF) ? 0 : output;
436 }
437 
438 int Y8950Adpcm::calcSample(bool doEmu)
439 {
440  // values taken from ymdelta.c by Tatsuyuki Satoh.
441  static constexpr int F1[16] = { 1, 3, 5, 7, 9, 11, 13, 15,
442  -1, -3, -5, -7, -9, -11, -13, -15 };
443  static constexpr int F2[16] = { 57, 57, 57, 57, 77, 102, 128, 153,
444  57, 57, 57, 57, 77, 102, 128, 153 };
445 
446  assert(isPlaying());
447 
448  PlayData& pd = doEmu ? emu : aud;
449  pd.nowStep += delta;
450  if (pd.nowStep & ~STEP_MASK) {
451  pd.nowStep &= STEP_MASK;
452  byte val;
453  if (!(pd.memPntr & 1)) {
454  // even nibble
455  if (reg7 & R07_MEMORY_DATA) {
456  pd.adpcm_data = readMemory(pd.memPntr);
457  } else {
458  pd.adpcm_data = reg15;
459  // set BRDY bit, ready to accept new data
460  if (doEmu) {
462  }
463  }
464  val = pd.adpcm_data >> 4;
465  } else {
466  // odd nibble
467  val = pd.adpcm_data & 0x0F;
468  }
469  int prevOut = pd.out;
470  pd.out = Math::clipIntToShort(pd.out + (pd.diff * F1[val]) / 8);
471  pd.diff = Math::clip<DMIN, DMAX>((pd.diff * F2[val]) / 64);
472 
473  int prevLeveling = pd.nextLeveling;
474  pd.nextLeveling = (prevOut + pd.out) / 2;
475  int deltaLeveling = pd.nextLeveling - prevLeveling;
476  pd.sampleStep = deltaLeveling * volumeWStep;
477  int tmp = deltaLeveling * ((volume * pd.nowStep) >> STEP_BITS);
478  pd.output = prevLeveling * volume + tmp;
479 
480  ++pd.memPntr;
481  if ((reg7 & R07_MEMORY_DATA) &&
482  (pd.memPntr > stopAddr)) {
483  // On 2003/06/21 I commited a patch with comment:
484  // generate end-of-sample interrupt at every sample
485  // end, including loops
486  // Unfortunatly it doesn't give any reason why and now
487  // I can't remember it :-(
488  // This is different from e.g. the MAME implementation.
489  if (doEmu) {
491  }
492  if (reg7 & R07_REPEAT) {
493  restart(pd);
494  } else {
495  if (doEmu) {
496  removeSyncPoint();
497  reg7 = 0;
498  }
499  }
500  }
501  } else {
502  pd.output += pd.sampleStep;
503  }
504  return pd.output >> 12;
505 }
506 
507 
508 // version 1:
509 // Initial verson
510 // version 2:
511 // - Split PlayData in emu and audio part (though this doesn't add new state
512 // to the savestate).
513 // - Added clock object.
514 template<typename Archive>
515 void Y8950Adpcm::serialize(Archive& ar, unsigned version)
516 {
517  ar.template serializeBase<Schedulable>(*this);
518  ar.serialize("ram", ram,
519  "startAddr", startAddr,
520  "stopAddr", stopAddr,
521  "addrMask", addrMask,
522  "volume", volume,
523  "volumeWStep", volumeWStep,
524  "readDelay", readDelay,
525  "delta", delta,
526  "reg7", reg7,
527  "reg15", reg15,
528  "romBank", romBank,
529 
530  "memPntr", emu.memPntr,
531  "nowStep", emu.nowStep,
532  "out", emu.out,
533  "output", emu.output,
534  "diff", emu.diff,
535  "nextLeveling", emu.nextLeveling,
536  "sampleStep", emu.sampleStep,
537  "adpcm_data", emu.adpcm_data);
538  if (ar.isLoader()) {
539  // ignore aud part for saving,
540  // for loading we make it the same as the emu part
541  aud = emu;
542  }
543 
544  if (ar.versionBelow(version, 2)) {
545  clock.reset(getCurrentTime());
546 
547  // reschedule, because automatically deserialized sync-point
548  // can be off, because clock.getTime() != getCurrentTime()
549  removeSyncPoint();
550  if (isPlaying()) {
551  schedule();
552  }
553  } else {
554  ar.serialize("clock", clock);
555  }
556 }
558 
559 } // namespace openmsx
openmsx::Clock::getTicksTill
constexpr unsigned getTicksTill(EmuTime::param e) const
Calculate the number of ticks for this clock until the given time.
Definition: Clock.hh:58
openmsx::R07_MEMORY_DATA
constexpr int R07_MEMORY_DATA
Definition: Y8950Adpcm.cc:25
openmsx::R07_START
constexpr int R07_START
Definition: Y8950Adpcm.cc:27
Clock.hh
openmsx::TrackedRam::clear
void clear(byte c=0xff)
Definition: TrackedRam.hh:43
openmsx::Y8950Adpcm::calcSample
int calcSample()
Definition: Y8950Adpcm.cc:430
serialize.hh
openmsx::Y8950Adpcm
Definition: Y8950Adpcm.hh:15
openmsx::Y8950Adpcm::Y8950Adpcm
Y8950Adpcm(Y8950 &y8950, const DeviceConfig &config, const std::string &name, unsigned sampleRam)
Definition: Y8950Adpcm.cc:46
openmsx::DeviceConfig
Definition: DeviceConfig.hh:19
openmsx::Y8950Adpcm::isMuted
bool isMuted() const
Definition: Y8950Adpcm.cc:88
openmsx::R08_DA_AD
constexpr int R08_DA_AD
Definition: Y8950Adpcm.cc:33
openmsx::R07_MODE
constexpr int R07_MODE
Definition: Y8950Adpcm.cc:28
gl::length
T length(const vecN< N, T > &x)
Definition: gl_vec.hh:348
openmsx::Y8950::resetStatus
void resetStatus(byte flags)
Definition: Y8950.cc:1242
openmsx::Schedulable
Every class that wants to get scheduled at some point must inherit from this class.
Definition: Schedulable.hh:33
openmsx::Y8950Adpcm::sync
void sync(EmuTime::param time)
Definition: Y8950Adpcm.cc:105
openmsx::R08_NOTE_SET
constexpr int R08_NOTE_SET
Definition: Y8950Adpcm.cc:35
openmsx::R08_SAMPL
constexpr int R08_SAMPL
Definition: Y8950Adpcm.cc:34
Y8950Adpcm.hh
openmsx::Y8950::setStatus
void setStatus(byte flags)
Definition: Y8950.cc:1234
Y8950.hh
openmsx::Y8950Adpcm::reset
void reset(EmuTime::param time)
Definition: Y8950Adpcm.cc:62
openmsx::Y8950::STATUS_PCM_BSY
static constexpr int STATUS_PCM_BSY
Definition: Y8950.hh:48
openmsx::Y8950Adpcm::readReg
byte readReg(byte rg, EmuTime::param time)
Definition: Y8950Adpcm.cc:300
openmsx::R08_CSM
constexpr int R08_CSM
Definition: Y8950Adpcm.cc:36
openmsx::Schedulable::setSyncPoint
void setSyncPoint(EmuTime::param timestamp)
Definition: Schedulable.cc:23
openmsx::R07_RESET
constexpr int R07_RESET
Definition: Y8950Adpcm.cc:22
openmsx::TrackedRam::getSize
unsigned getSize() const
Definition: TrackedRam.hh:20
openmsx::Y8950Adpcm::clearRam
void clearRam()
Definition: Y8950Adpcm.cc:57
openmsx::R07_SP_OFF
constexpr int R07_SP_OFF
Definition: Y8950Adpcm.cc:23
INSTANTIATE_SERIALIZE_METHODS
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:981
openmsx::R07_REPEAT
constexpr int R07_REPEAT
Definition: Y8950Adpcm.cc:24
openmsx::DMAX
constexpr int DMAX
Definition: Y8950Adpcm.cc:38
openmsx::Y8950::STATUS_BUF_RDY
static constexpr int STATUS_BUF_RDY
Definition: Y8950.hh:50
openmsx::R08_ROM
constexpr int R08_ROM
Definition: Y8950Adpcm.cc:31
openmsx::Y8950Adpcm::resetStatus
void resetStatus()
Definition: Y8950Adpcm.cc:332
openmsx::Y8950Adpcm::writeReg
void writeReg(byte rg, byte data, EmuTime::param time)
Definition: Y8950Adpcm.cc:150
openmsx::STEP_MASK
constexpr int STEP_MASK
Definition: Y8950Adpcm.cc:43
openmsx::Y8950::STATUS_EOS
static constexpr int STATUS_EOS
Definition: Y8950.hh:49
openmsx::DMIN
constexpr int DMIN
Definition: Y8950Adpcm.cc:39
openmsx::TrackedRam::write
void write(unsigned addr, byte value)
Definition: TrackedRam.hh:38
openmsx::Clock::reset
constexpr void reset(EmuTime::param e)
Reset the clock to start ticking at the given time.
Definition: Clock.hh:102
openmsx::Clock::advance
constexpr void advance(EmuTime::param e)
Advance this clock in time until the last tick which is not past the given time.
Definition: Clock.hh:110
Math::clipIntToShort
int16_t clipIntToShort(int x)
Clip x to range [-32768,32767].
Definition: Math.hh:111
openmsx::DDEF
constexpr int DDEF
Definition: Y8950Adpcm.cc:40
openmsx::Schedulable::getCurrentTime
EmuTime::param getCurrentTime() const
Convenience method: This is the same as getScheduler().getCurrentTime().
Definition: Schedulable.cc:49
openmsx::Y8950Adpcm::serialize
void serialize(Archive &ar, unsigned version)
Definition: Y8950Adpcm.cc:515
openmsx::R08_64K
constexpr int R08_64K
Definition: Y8950Adpcm.cc:32
openmsx::Y8950Adpcm::peekReg
byte peekReg(byte rg, EmuTime::param time) const
Definition: Y8950Adpcm.cc:309
DeviceConfig.hh
Math.hh
openmsx::Clock
Represents a clock with a fixed frequency.
Definition: Clock.hh:18
openmsx::STEP_BITS
constexpr int STEP_BITS
Definition: Y8950Adpcm.cc:42
openmsx::R07_REC
constexpr int R07_REC
Definition: Y8950Adpcm.cc:26
openmsx
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
openmsx::Y8950::peekRawStatus
byte peekRawStatus() const
Definition: Y8950.cc:1250
MSXMotherBoard.hh
openmsx::Schedulable::removeSyncPoint
bool removeSyncPoint()
Definition: Schedulable.cc:28
openmsx::Y8950
Definition: Y8950.hh:23