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