]> Shamusworld >> Repos - virtualjaguar/blob - src/dac.cpp
983680791fccefe73613246120633f4bf8543e95
[virtualjaguar] / src / dac.cpp
1 //
2 // DAC (really, Synchronous Serial Interface) Handler
3 //
4 // Originally by David Raingeard
5 // GCC/SDL port by Niels Wagenaar (Linux/WIN32) and Caz (BeOS)
6 // Rewritten by James Hammons
7 // (C) 2010 Underground Software
8 //
9 // JLH = James Hammons <jlhamm@acm.org>
10 //
11 // Who  When        What
12 // ---  ----------  -------------------------------------------------------------
13 // JLH  01/16/2010  Created this log ;-)
14 //
15
16 // Need to set up defaults that the BIOS sets for the SSI here in DACInit()... !!! FIX !!!
17 // or something like that... Seems like it already does, but it doesn't seem to
18 // work correctly...! Perhaps just need to set up SSI stuff so BUTCH doesn't get
19 // confused...
20
21 // ALSO: Need to implement some form of proper locking to replace the clusterfuck
22 //       that is the current spinlock implementation. Since the DSP is a separate
23 //       entity, could we get away with running it in the sound IRQ?
24
25 // ALSO: It may be a good idea to physically separate the left and right buffers
26 //       to prevent things like the DSP filling only one side and such. Do such
27 //       mono modes exist on the Jag? Seems to according to Super Burnout.
28
29 // After testing on a real Jaguar, it seems clear that the I2S interrupt drives
30 // the audio subsystem. So while you can drive the audio at a *slower* rate than
31 // set by SCLK, you can't drive it any *faster*. Also note, that if the I2S
32 // interrupt is not enabled/running on the DSP, then there is no audio. Also,
33 // audio can be muted by clearing bit 8 of JOYSTICK (JOY1).
34 //
35 // Approach: We can run the DSP in the host system's audio IRQ, by running the
36 // DSP for the alloted time (depending on the host buffer size & sample rate)
37 // by simply reading the L/R_I2S (L/RTXD) registers at regular intervals. We
38 // would also have to time the I2S/TIMER0/TIMER1 interrupts in the DSP as well.
39 // This way, we can run the host audio IRQ at, say, 48 KHz and not have to care
40 // so much about SCLK and running a separate buffer and all the attendant
41 // garbage that comes with that awful approach.
42 //
43 // There would still be potential gotchas, as the SCLK can theoretically drive
44 // the I2S at 26590906 / 2 (for SCLK == 0) = 13.3 MHz which corresponds to an
45 // audio rate 416 KHz (dividing the I2S rate by 32, for 16-bit stereo). It
46 // seems doubtful that anything useful could come of such a high rate, and we
47 // can probably safely ignore any such ridiculously high audio rates. It won't
48 // sound the same as on a real Jaguar, but who cares? :-)
49
50 #include "dac.h"
51
52 #include "SDL.h"
53 //#include "gui.h"
54 #include "jaguar.h"
55 #include "log.h"
56 #include "m68k.h"
57 //#include "memory.h"
58 #include "settings.h"
59
60 //#define DEBUG_DAC
61
62 #define BUFFER_SIZE             0x10000                                         // Make the DAC buffers 64K x 16 bits
63
64 // Jaguar memory locations
65
66 #define LTXD                    0xF1A148
67 #define RTXD                    0xF1A14C
68 #define LRXD                    0xF1A148
69 #define RRXD                    0xF1A14C
70 #define SCLK                    0xF1A150
71 #define SMODE                   0xF1A154
72
73 // Global variables
74
75 //uint16 lrxd, rrxd;                                                                    // I2S ports (into Jaguar)
76
77 // Local variables
78
79 static uint32 LeftFIFOHeadPtr, LeftFIFOTailPtr, RightFIFOHeadPtr, RightFIFOTailPtr;
80 static SDL_AudioSpec desired;
81 static bool SDLSoundInitialized;
82
83 // We can get away with using native endian here because we can tell SDL to use the native
84 // endian when looking at the sample buffer, i.e., no need to worry about it.
85
86 static uint16 DACBuffer[BUFFER_SIZE];
87 static uint8 SCLKFrequencyDivider = 19;                         // Default is roughly 22 KHz (20774 Hz in NTSC mode)
88 /*static*/ uint16 serialMode = 0;
89
90 // Private function prototypes
91
92 void SDLSoundCallback(void * userdata, Uint8 * buffer, int length);
93
94 //
95 // Initialize the SDL sound system
96 //
97 void DACInit(void)
98 {
99         SDLSoundInitialized = false;
100
101         if (!vjs.audioEnabled)
102         {
103                 WriteLog("DAC: Host audio playback disabled.\n");
104                 return;
105         }
106
107 //      memory_malloc_secure((void **)&DACBuffer, BUFFER_SIZE * sizeof(uint16), "DAC buffer");
108 //      DACBuffer = (uint16 *)memory_malloc(BUFFER_SIZE * sizeof(uint16), "DAC buffer");
109
110         desired.freq = GetCalculatedFrequency();                // SDL will do conversion on the fly, if it can't get the exact rate. Nice!
111         desired.format = AUDIO_S16SYS;                                  // This uses the native endian (for portability)...
112         desired.channels = 2;
113 //      desired.samples = 4096;                                                 // Let's try a 4K buffer (can always go lower)
114         desired.samples = 2048;                                                 // Let's try a 2K buffer (can always go lower)
115         desired.callback = SDLSoundCallback;
116
117         if (SDL_OpenAudio(&desired, NULL) < 0)                  // NULL means SDL guarantees what we want
118                 WriteLog("DAC: Failed to initialize SDL sound...\n");
119         else
120         {
121                 SDLSoundInitialized = true;
122                 DACReset();
123                 SDL_PauseAudio(false);                                                  // Start playback!
124                 WriteLog("DAC: Successfully initialized.\n");
125         }
126 }
127
128 //
129 // Reset the sound buffer FIFOs
130 //
131 void DACReset(void)
132 {
133         LeftFIFOHeadPtr = LeftFIFOTailPtr = 0, RightFIFOHeadPtr = RightFIFOTailPtr = 1;
134 }
135
136 //
137 // Close down the SDL sound subsystem
138 //
139 void DACDone(void)
140 {
141         if (SDLSoundInitialized)
142         {
143                 SDL_PauseAudio(true);
144                 SDL_CloseAudio();
145         }
146
147 //      memory_free(DACBuffer);
148         WriteLog("DAC: Done.\n");
149 }
150
151
152 // Approach: Run the DSP for however many cycles needed to correspond to whatever sample rate
153 // we've set the audio to run at. So, e.g., if we run it at 48 KHz, then we would run the DSP
154 // for however much time it takes to fill the buffer. So with a 2K buffer, this would correspond
155 // to running the DSP for 0.042666... seconds. At 26590906 Hz, this would correspond to
156 // running the DSP for 1134545 cycles. You would then sample the L/RTXD registers every
157 // 1134545 / 2048 = 554 cycles to fill the buffer. You would also have to manage interrupt
158 // timing as well (generating them at the proper times), but that shouldn't be too difficult...
159 // If the DSP isn't running, then fill the buffer with L/RTXD and exit.
160
161 //
162 // SDL callback routine to fill audio buffer
163 //
164 // Note: The samples are packed in the buffer in 16 bit left/16 bit right pairs.
165 //
166 void SDLSoundCallback(void * userdata, Uint8 * buffer, int length)
167 {
168         // Clear the buffer to silence, in case the DAC buffer is empty (or short)
169 //This causes choppy sound... Ick.
170         memset(buffer, desired.silence, length);
171 //WriteLog("DAC: Inside callback...\n");
172         if (LeftFIFOHeadPtr != LeftFIFOTailPtr)
173         {
174 //WriteLog("DAC: About to write some data!\n");
175                 int numLeftSamplesReady
176                         = (LeftFIFOTailPtr + (LeftFIFOTailPtr < LeftFIFOHeadPtr ? BUFFER_SIZE : 0))
177                                 - LeftFIFOHeadPtr;
178                 int numRightSamplesReady
179                         = (RightFIFOTailPtr + (RightFIFOTailPtr < RightFIFOHeadPtr ? BUFFER_SIZE : 0))
180                                 - RightFIFOHeadPtr;
181 //This waits for the slower side to catch up. If writing only one side, then this
182 //causes the buffer not to drain...
183                 int numSamplesReady
184                         = (numLeftSamplesReady < numRightSamplesReady
185                                 ? numLeftSamplesReady : numRightSamplesReady);//Hmm. * 2;
186
187 //Kludge, until I can figure out WTF is going on WRT Super Burnout.
188 if (numLeftSamplesReady == 0 || numRightSamplesReady == 0)
189         numSamplesReady = numLeftSamplesReady + numRightSamplesReady;
190
191 //The numbers look good--it's just that the DSP can't get enough samples in the DAC buffer!
192 //WriteLog("DAC: Left/RightFIFOHeadPtr: %u/%u, Left/RightFIFOTailPtr: %u/%u\n", LeftFIFOHeadPtr, RightFIFOHeadPtr, LeftFIFOTailPtr, RightFIFOTailPtr);
193 //WriteLog("     numLeft/RightSamplesReady: %i/%i, numSamplesReady: %i, length of buffer: %i\n", numLeftSamplesReady, numRightSamplesReady, numSamplesReady, length);
194
195 /*              if (numSamplesReady > length)
196                         numSamplesReady = length;//*/
197                 if (numSamplesReady > length / 2)       // length / 2 because we're comparing 16-bit lengths
198                         numSamplesReady = length / 2;
199 //else
200 //      WriteLog("     Not enough samples to fill the buffer (short by %u L/R samples)...\n", (length / 2) - numSamplesReady);
201 //WriteLog("DAC: %u samples ready.\n", numSamplesReady);
202
203                 // Actually, it's a bit more involved than this, but this is the general idea:
204 //              memcpy(buffer, DACBuffer, length);
205                 for(int i=0; i<numSamplesReady; i++)
206                         ((uint16 *)buffer)[i] = DACBuffer[(LeftFIFOHeadPtr + i) % BUFFER_SIZE];
207                         // Could also use (as long as BUFFER_SIZE is a multiple of 2):
208 //                      buffer[i] = DACBuffer[(LeftFIFOHeadPtr + i) & (BUFFER_SIZE - 1)];
209
210                 LeftFIFOHeadPtr = (LeftFIFOHeadPtr + numSamplesReady) % BUFFER_SIZE;
211                 RightFIFOHeadPtr = (RightFIFOHeadPtr + numSamplesReady) % BUFFER_SIZE;
212                 // Could also use (as long as BUFFER_SIZE is a multiple of 2):
213 //              LeftFIFOHeadPtr = (LeftFIFOHeadPtr + numSamplesReady) & (BUFFER_SIZE - 1);
214 //              RightFIFOHeadPtr = (RightFIFOHeadPtr + numSamplesReady) & (BUFFER_SIZE - 1);
215 //WriteLog("  -> Left/RightFIFOHeadPtr: %04X/%04X, Left/RightFIFOTailPtr: %04X/%04X\n", LeftFIFOHeadPtr, RightFIFOHeadPtr, LeftFIFOTailPtr, RightFIFOTailPtr);
216         }
217 //Hmm. Seems that the SDL buffer isn't being starved by the DAC buffer...
218 //      else
219 //              WriteLog("DAC: Silence...!\n");
220 }
221
222 //
223 // Calculate the frequency of SCLK * 32 using the divider
224 //
225 int GetCalculatedFrequency(void)
226 {
227         int systemClockFrequency = (vjs.hardwareTypeNTSC ? RISC_CLOCK_RATE_NTSC : RISC_CLOCK_RATE_PAL);
228
229         // We divide by 32 here in order to find the frequency of 32 SCLKs in a row (transferring
230         // 16 bits of left data + 16 bits of right data = 32 bits, 1 SCLK = 1 bit transferred).
231         return systemClockFrequency / (32 * (2 * (SCLKFrequencyDivider + 1)));
232 }
233
234 static int oldFreq = 0;
235
236 void DACSetNewFrequency(int freq)
237 {
238         if (freq == oldFreq)
239                 return;
240
241         oldFreq = freq;
242
243         // Should do some sanity checking on the frequency...
244
245         if (SDLSoundInitialized)
246                 SDL_CloseAudio();
247
248         desired.freq = freq;// SDL will do conversion on the fly, if it can't get the exact rate. Nice!
249         WriteLog("DAC: Changing sample rate to %u Hz!\n", desired.freq);
250
251         if (SDLSoundInitialized)
252         {
253                 if (SDL_OpenAudio(&desired, NULL) < 0)  // NULL means SDL guarantees what we want
254                 {
255 // This is bad, Bad, BAD !!! DON'T ABORT BECAUSE WE DIDN'T GET OUR FREQ! !!! FIX !!!
256 #warning !!! FIX !!! Aborting because of SDL audio problem is bad!
257                         WriteLog("DAC: Failed to initialize SDL sound: %s.\nDesired freq: %u\nShutting down!\n", SDL_GetError(), desired.freq);
258 //                                              LogDone();
259 //                                              exit(1);
260 #warning "Reimplement GUICrashGracefully!"
261 //                                              GUICrashGracefully("Failed to initialize SDL sound!");
262                         return;
263                 }
264         }
265
266         DACReset();
267
268         if (SDLSoundInitialized)
269                 SDL_PauseAudio(false);                  // Start playback!
270 }
271
272 //
273 // LTXD/RTXD/SCLK/SMODE ($F1A148/4C/50/54)
274 //
275 void DACWriteByte(uint32 offset, uint8 data, uint32 who/*= UNKNOWN*/)
276 {
277         WriteLog("DAC: %s writing BYTE %02X at %08X\n", whoName[who], data, offset);
278         if (offset == SCLK + 3)
279                 DACWriteWord(offset - 3, (uint16)data);
280 }
281
282 void DACWriteWord(uint32 offset, uint16 data, uint32 who/*= UNKNOWN*/)
283 {
284         if (offset == LTXD + 2)
285         {
286                 if (!SDLSoundInitialized)
287                         return;
288                 // Spin until buffer has been drained (for too fast processors!)...
289 //Small problem--if Head == 0 and Tail == buffer end, then this will fail... !!! FIX !!!
290 //[DONE]
291                 // Also, we're taking advantage of the fact that the buffer is a multiple of two
292                 // in this check...
293 uint32 spin = 0;
294                 while (((LeftFIFOTailPtr + 2) & (BUFFER_SIZE - 1)) == LeftFIFOHeadPtr)//;
295                 {
296 spin++;
297 //if ((spin & 0x0FFFFFFF) == 0)
298 //      WriteLog("Tail=%X, Head=%X, BUFFER_SIZE-1=%X\n", RightFIFOTailPtr, RightFIFOHeadPtr, BUFFER_SIZE - 1);
299
300 if (spin == 0xFFFF0000)
301 {
302 uint32 ltail = LeftFIFOTailPtr, lhead = LeftFIFOHeadPtr;
303 WriteLog("Tail=%X, Head=%X", ltail, lhead);
304
305         WriteLog("\nStuck in left DAC spinlock! Aborting!\n");
306         WriteLog("LTail=%X, LHead=%X, BUFFER_SIZE-1=%X\n", LeftFIFOTailPtr, LeftFIFOHeadPtr, BUFFER_SIZE - 1);
307         WriteLog("RTail=%X, RHead=%X, BUFFER_SIZE-1=%X\n", RightFIFOTailPtr, RightFIFOHeadPtr, BUFFER_SIZE - 1);
308         WriteLog("From while: Tail=%X, Head=%X", (LeftFIFOTailPtr + 2) & (BUFFER_SIZE - 1), LeftFIFOHeadPtr);
309 //      LogDone();
310 //      exit(0);
311 #warning "Reimplement GUICrashGracefully!"
312 //      GUICrashGracefully("Stuck in left DAC spinlock!");
313         return;
314 }
315                 }//*/
316
317                 SDL_LockAudio();                                                        // Is it necessary to do this? Mebbe.
318                 // We use a circular buffer 'cause it's easy. Note that the callback function
319                 // takes care of dumping audio to the soundcard...! Also note that we're writing
320                 // the samples in the buffer in an interleaved L/R format.
321                 LeftFIFOTailPtr = (LeftFIFOTailPtr + 2) % BUFFER_SIZE;
322                 DACBuffer[LeftFIFOTailPtr] = data;
323                 SDL_UnlockAudio();
324         }
325         else if (offset == RTXD + 2)
326         {
327                 if (!SDLSoundInitialized)
328                         return;
329 /*
330 Here's what's happening now:
331
332 Stuck in right DAC spinlock!
333 Aborting!
334
335 Tail=681, Head=681, BUFFER_SIZE-1=FFFF
336 From while: Tail=683, Head=681
337
338 ????? What the FUCK ?????
339
340 & when I uncomment the lines below spin++; it *doesn't* lock here... WTF?????
341
342 I think it was missing parentheses causing the fuckup... Seems to work now...
343
344 Except for Super Burnout now...! Aarrrgggghhhhh!
345
346 Tail=AC, Head=AE
347 Stuck in left DAC spinlock! Aborting!
348 Tail=AC, Head=AE, BUFFER_SIZE-1=FFFF
349 From while: Tail=AE, Head=AE
350
351 So it's *really* stuck here in the left FIFO. Figure out why!!!
352
353 Prolly 'cause it doesn't set the sample rate right away--betcha it works with the BIOS...
354 It gets farther, but then locks here (weird!):
355
356 Tail=2564, Head=2566
357 Stuck in left DAC spinlock! Aborting!
358 Tail=2564, Head=2566, BUFFER_SIZE-1=FFFF
359 From while: Tail=2566, Head=2566
360
361 Weird--recompile with more WriteLog() entries and it *doesn't* lock...
362 Yeah, because there was no DSP running. Duh!
363
364 Tail=AC, Head=AE
365 Stuck in left DAC spinlock! Aborting!
366 LTail=AC, LHead=AE, BUFFER_SIZE-1=FFFF
367 RTail=AF, RHead=AF, BUFFER_SIZE-1=FFFF
368 From while: Tail=AE, Head=AE
369
370 Odd: The right FIFO is empty, but the left FIFO is full!
371 And this is what is causing the lockup--the DAC callback waits for the side with
372 less samples ready and in this case it's the right channel (that never fills up)
373 that it's waiting for...!
374
375 Okay, with the kludge in place for the right channel not being filled, we select
376 a track and then it locks here:
377
378 Tail=60D8, Head=60DA
379 Stuck in left DAC spinlock! Aborting!
380 LTail=60D8, LHead=60D8, BUFFER_SIZE-1=FFFF
381 RTail=DB, RHead=60D9, BUFFER_SIZE-1=FFFF
382 From while: Tail=60DA, Head=60D8
383 */
384 #warning Spinlock problem--!!! FIX !!!
385 #warning Odd: The right FIFO is empty, but the left FIFO is full!
386                 // Spin until buffer has been drained (for too fast processors!)...
387 uint32 spin = 0;
388                 while (((RightFIFOTailPtr + 2) & (BUFFER_SIZE - 1)) == RightFIFOHeadPtr)//;
389                 {
390 spin++;
391 //if ((spin & 0x0FFFFFFF) == 0)
392 //      WriteLog("Tail=%X, Head=%X, BUFFER_SIZE-1=%X\n", RightFIFOTailPtr, RightFIFOHeadPtr, BUFFER_SIZE - 1);
393
394 if (spin == 0xFFFF0000)
395 {
396 uint32 rtail = RightFIFOTailPtr, rhead = RightFIFOHeadPtr;
397 WriteLog("Tail=%X, Head=%X", rtail, rhead);
398
399         WriteLog("\nStuck in right DAC spinlock! Aborting!\n");
400         WriteLog("LTail=%X, LHead=%X, BUFFER_SIZE-1=%X\n", LeftFIFOTailPtr, LeftFIFOHeadPtr, BUFFER_SIZE - 1);
401         WriteLog("RTail=%X, RHead=%X, BUFFER_SIZE-1=%X\n", RightFIFOTailPtr, RightFIFOHeadPtr, BUFFER_SIZE - 1);
402         WriteLog("From while: Tail=%X, Head=%X", (RightFIFOTailPtr + 2) & (BUFFER_SIZE - 1), RightFIFOHeadPtr);
403 //      LogDone();
404 //      exit(0);
405 #warning "Reimplement GUICrashGracefully!"
406 //      GUICrashGracefully("Stuck in right DAC spinlock!");
407         return;
408 }
409                 }//*/
410
411                 SDL_LockAudio();
412                 RightFIFOTailPtr = (RightFIFOTailPtr + 2) % BUFFER_SIZE;
413                 DACBuffer[RightFIFOTailPtr] = data;
414                 SDL_UnlockAudio();
415 /*#ifdef DEBUG_DAC
416                 else
417                         WriteLog("DAC: Ran into FIFO's right tail pointer!\n");
418 #endif*/
419         }
420         else if (offset == SCLK + 2)                                    // Sample rate
421         {
422                 WriteLog("DAC: Writing %u to SCLK...\n", data);
423                 if ((uint8)data != SCLKFrequencyDivider)
424                 {
425                         SCLKFrequencyDivider = (uint8)data;
426 //Of course a better way would be to query the hardware to find the upper limit...
427                         if (data > 7)   // Anything less than 8 is too high!
428                         {
429                                 if (SDLSoundInitialized)
430                                         SDL_CloseAudio();
431
432                                 desired.freq = GetCalculatedFrequency();// SDL will do conversion on the fly, if it can't get the exact rate. Nice!
433                                 WriteLog("DAC: Changing sample rate to %u Hz!\n", desired.freq);
434
435                                 if (SDLSoundInitialized)
436                                 {
437                                         if (SDL_OpenAudio(&desired, NULL) < 0)  // NULL means SDL guarantees what we want
438                                         {
439 // This is bad, Bad, BAD !!! DON'T ABORT BECAUSE WE DIDN'T GET OUR FREQ! !!! FIX !!!
440 #warning !!! FIX !!! Aborting because of SDL audio problem is bad!
441                                                 WriteLog("DAC: Failed to initialize SDL sound: %s.\nDesired freq: %u\nShutting down!\n", SDL_GetError(), desired.freq);
442 //                                              LogDone();
443 //                                              exit(1);
444 #warning "Reimplement GUICrashGracefully!"
445 //                                              GUICrashGracefully("Failed to initialize SDL sound!");
446                                                 return;
447                                         }
448                                 }
449
450                                 DACReset();
451
452                                 if (SDLSoundInitialized)
453                                         SDL_PauseAudio(false);                  // Start playback!
454                         }
455                 }
456         }
457         else if (offset == SMODE + 2)
458         {
459                 serialMode = data;
460                 WriteLog("DAC: %s writing to SMODE. Bits: %s%s%s%s%s%s [68K PC=%08X]\n", whoName[who],
461                         (data & 0x01 ? "INTERNAL " : ""), (data & 0x02 ? "MODE " : ""),
462                         (data & 0x04 ? "WSEN " : ""), (data & 0x08 ? "RISING " : ""),
463                         (data & 0x10 ? "FALLING " : ""), (data & 0x20 ? "EVERYWORD" : ""),
464                         m68k_get_reg(NULL, M68K_REG_PC));
465         }
466 }
467
468 //
469 // LRXD/RRXD/SSTAT ($F1A148/4C/50)
470 //
471 uint8 DACReadByte(uint32 offset, uint32 who/*= UNKNOWN*/)
472 {
473 //      WriteLog("DAC: %s reading byte from %08X\n", whoName[who], offset);
474         return 0xFF;
475 }
476
477 //static uint16 fakeWord = 0;
478 uint16 DACReadWord(uint32 offset, uint32 who/*= UNKNOWN*/)
479 {
480 //      WriteLog("DAC: %s reading word from %08X\n", whoName[who], offset);
481 //      return 0xFFFF;
482 //      WriteLog("DAC: %s reading WORD %04X from %08X\n", whoName[who], fakeWord, offset);
483 //      return fakeWord++;
484 //NOTE: This only works if a bunch of things are set in BUTCH which we currently don't
485 //      check for. !!! FIX !!!
486 // Partially fixed: We check for I2SCNTRL in the JERRY I2S routine...
487 //      return GetWordFromButchSSI(offset, who);
488         if (offset == LRXD || offset == RRXD)
489                 return 0x0000;
490         else if (offset == LRXD + 2)
491                 return lrxd;
492         else if (offset == RRXD + 2)
493                 return rrxd;
494
495         return 0xFFFF;  // May need SSTAT as well... (but may be a Jaguar II only feature)
496 }