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
20namespace openmsx {
21
22// Bitmask for register 0x07
23constexpr int R07_RESET = 0x01;
24constexpr int R07_SP_OFF = 0x08;
25constexpr int R07_REPEAT = 0x10;
26constexpr int R07_MEMORY_DATA = 0x20;
27constexpr int R07_REC = 0x40;
28constexpr int R07_START = 0x80;
29constexpr int R07_MODE = 0xE0;
30
31// Bitmask for register 0x08
32constexpr int R08_ROM = 0x01;
33constexpr int R08_64K = 0x02;
34constexpr int R08_DA_AD = 0x04;
35constexpr int R08_SAMPL = 0x08;
36constexpr int R08_NOTE_SET = 0x40;
37constexpr int R08_CSM = 0x80;
38
39constexpr int DMAX = 0x6000;
40constexpr int DMIN = 0x7F;
41constexpr int DDEF = 0x7F;
42
43constexpr int STEP_BITS = 16;
44constexpr int STEP_MASK = (1 << STEP_BITS) -1;
45
46
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
63void 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
85bool Y8950Adpcm::isPlaying() const
86{
87 return (reg7 & 0xC0) == 0x80;
88}
90{
91 return !isPlaying() || (reg7 & R07_SP_OFF);
92}
93
94void 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
106void 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
117void 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
141void 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
151void 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 }
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()) {
206 schedule();
207 }
208 break;
209 case 0x0C: // STOP ADDRESS (H)
210 stopAddr = (stopAddr & 0x007FF) | (data << 11);
211 if (isPlaying()) {
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()) {
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()) {
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
254void 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
301byte 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
310byte 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
316byte 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
358byte 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
397byte 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
414void 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}
421byte 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
439int 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) {
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.
516template<typename Archive>
517void 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 constexpr (Archive::IS_LOADER) {
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()
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:43
T length(const vecN< N, T > &x)
Definition: gl_vec.hh:339
constexpr vecN< N, T > clamp(const vecN< N, T > &x, const vecN< N, T > &minVal, const vecN< N, T > &maxVal)
Definition: gl_vec.hh:292
This file implemented 3 utility functions:
Definition: Autofire.cc:9
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:1009