]> Shamusworld >> Repos - virtualjaguar/blob - src/dac.cpp
Initial changeset to experimental branch
[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 L. Hammons
7 // (C) 2010 Underground Software
8 //
9 // JLH = James L. 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 #include "dac.h"
30
31 #include "SDL.h"
32 //#include "gui.h"
33 #include "jaguar.h"
34 #include "log.h"
35 #include "m68k.h"
36 //#include "memory.h"
37 #include "settings.h"
38
39 //#define DEBUG_DAC
40
41 #define BUFFER_SIZE             0x10000                                         // Make the DAC buffers 64K x 16 bits
42
43 // Jaguar memory locations
44
45 #define LTXD                    0xF1A148
46 #define RTXD                    0xF1A14C
47 #define LRXD                    0xF1A148
48 #define RRXD                    0xF1A14C
49 #define SCLK                    0xF1A150
50 #define SMODE                   0xF1A154
51
52 // Global variables
53
54 uint16 lrxd, rrxd;                                                                      // I2S ports (into Jaguar)
55
56 // Local variables
57
58 static uint32 LeftFIFOHeadPtr, LeftFIFOTailPtr, RightFIFOHeadPtr, RightFIFOTailPtr;
59 static SDL_AudioSpec desired;
60 static bool SDLSoundInitialized = false;
61
62 // We can get away with using native endian here because we can tell SDL to use the native
63 // endian when looking at the sample buffer, i.e., no need to worry about it.
64
65 static uint16 DACBuffer[BUFFER_SIZE];
66 static uint8 SCLKFrequencyDivider = 19;                         // Default is roughly 22 KHz (20774 Hz in NTSC mode)
67 /*static*/ uint16 serialMode = 0;
68
69 // Private function prototypes
70
71 void SDLSoundCallback(void * userdata, Uint8 * buffer, int length);
72 int GetCalculatedFrequency(void);
73
74 //
75 // Initialize the SDL sound system
76 //
77 void DACInit(void)
78 {
79 //      memory_malloc_secure((void **)&DACBuffer, BUFFER_SIZE * sizeof(uint16), "DAC buffer");
80 //      DACBuffer = (uint16 *)memory_malloc(BUFFER_SIZE * sizeof(uint16), "DAC buffer");
81
82         desired.freq = GetCalculatedFrequency();                // SDL will do conversion on the fly, if it can't get the exact rate. Nice!
83         desired.format = AUDIO_S16SYS;                                  // This uses the native endian (for portability)...
84         desired.channels = 2;
85 //      desired.samples = 4096;                                                 // Let's try a 4K buffer (can always go lower)
86         desired.samples = 2048;                                                 // Let's try a 2K buffer (can always go lower)
87         desired.callback = SDLSoundCallback;
88
89         if (SDL_OpenAudio(&desired, NULL) < 0)                  // NULL means SDL guarantees what we want
90                 WriteLog("DAC: Failed to initialize SDL sound...\n");
91         else
92         {
93                 SDLSoundInitialized = true;
94                 DACReset();
95                 SDL_PauseAudio(false);                                                  // Start playback!
96                 WriteLog("DAC: Successfully initialized.\n");
97         }
98 }
99
100 //
101 // Reset the sound buffer FIFOs
102 //
103 void DACReset(void)
104 {
105         LeftFIFOHeadPtr = LeftFIFOTailPtr = 0, RightFIFOHeadPtr = RightFIFOTailPtr = 1;
106 }
107
108 //
109 // Close down the SDL sound subsystem
110 //
111 void DACDone(void)
112 {
113         if (SDLSoundInitialized)
114         {
115                 SDL_PauseAudio(true);
116                 SDL_CloseAudio();
117         }
118
119 //      memory_free(DACBuffer);
120         WriteLog("DAC: Done.\n");
121 }
122
123 //
124 // SDL callback routine to fill audio buffer
125 //
126 // Note: The samples are packed in the buffer in 16 bit left/16 bit right pairs.
127 //
128 void SDLSoundCallback(void * userdata, Uint8 * buffer, int length)
129 {
130         // Clear the buffer to silence, in case the DAC buffer is empty (or short)
131 //This causes choppy sound... Ick.
132         memset(buffer, desired.silence, length);
133 //WriteLog("DAC: Inside callback...\n");
134         if (LeftFIFOHeadPtr != LeftFIFOTailPtr)
135         {
136 //WriteLog("DAC: About to write some data!\n");
137                 int numLeftSamplesReady
138                         = (LeftFIFOTailPtr + (LeftFIFOTailPtr < LeftFIFOHeadPtr ? BUFFER_SIZE : 0))
139                                 - LeftFIFOHeadPtr;
140                 int numRightSamplesReady
141                         = (RightFIFOTailPtr + (RightFIFOTailPtr < RightFIFOHeadPtr ? BUFFER_SIZE : 0))
142                                 - RightFIFOHeadPtr;
143 //This waits for the slower side to catch up. If writing only one side, then this
144 //causes the buffer not to drain...
145                 int numSamplesReady
146                         = (numLeftSamplesReady < numRightSamplesReady
147                                 ? numLeftSamplesReady : numRightSamplesReady);//Hmm. * 2;
148
149 //Kludge, until I can figure out WTF is going on WRT Super Burnout.
150 if (numLeftSamplesReady == 0 || numRightSamplesReady == 0)
151         numSamplesReady = numLeftSamplesReady + numRightSamplesReady;
152
153 //The numbers look good--it's just that the DSP can't get enough samples in the DAC buffer!
154 //WriteLog("DAC: Left/RightFIFOHeadPtr: %u/%u, Left/RightFIFOTailPtr: %u/%u\n", LeftFIFOHeadPtr, RightFIFOHeadPtr, LeftFIFOTailPtr, RightFIFOTailPtr);
155 //WriteLog("     numLeft/RightSamplesReady: %i/%i, numSamplesReady: %i, length of buffer: %i\n", numLeftSamplesReady, numRightSamplesReady, numSamplesReady, length);
156
157 /*              if (numSamplesReady > length)
158                         numSamplesReady = length;//*/
159                 if (numSamplesReady > length / 2)       // length / 2 because we're comparing 16-bit lengths
160                         numSamplesReady = length / 2;
161 //else
162 //      WriteLog("     Not enough samples to fill the buffer (short by %u L/R samples)...\n", (length / 2) - numSamplesReady);
163 //WriteLog("DAC: %u samples ready.\n", numSamplesReady);
164
165                 // Actually, it's a bit more involved than this, but this is the general idea:
166 //              memcpy(buffer, DACBuffer, length);
167                 for(int i=0; i<numSamplesReady; i++)
168                         ((uint16 *)buffer)[i] = DACBuffer[(LeftFIFOHeadPtr + i) % BUFFER_SIZE];
169                         // Could also use (as long as BUFFER_SIZE is a multiple of 2):
170 //                      buffer[i] = DACBuffer[(LeftFIFOHeadPtr + i) & (BUFFER_SIZE - 1)];
171
172                 LeftFIFOHeadPtr = (LeftFIFOHeadPtr + numSamplesReady) % BUFFER_SIZE;
173                 RightFIFOHeadPtr = (RightFIFOHeadPtr + numSamplesReady) % BUFFER_SIZE;
174                 // Could also use (as long as BUFFER_SIZE is a multiple of 2):
175 //              LeftFIFOHeadPtr = (LeftFIFOHeadPtr + numSamplesReady) & (BUFFER_SIZE - 1);
176 //              RightFIFOHeadPtr = (RightFIFOHeadPtr + numSamplesReady) & (BUFFER_SIZE - 1);
177 //WriteLog("  -> Left/RightFIFOHeadPtr: %04X/%04X, Left/RightFIFOTailPtr: %04X/%04X\n", LeftFIFOHeadPtr, RightFIFOHeadPtr, LeftFIFOTailPtr, RightFIFOTailPtr);
178         }
179 //Hmm. Seems that the SDL buffer isn't being starved by the DAC buffer...
180 //      else
181 //              WriteLog("DAC: Silence...!\n");
182 }
183
184 //
185 // Calculate the freq9uency of SCLK * 32 using the divider
186 //
187 int GetCalculatedFrequency(void)
188 {
189         int systemClockFrequency = (vjs.hardwareTypeNTSC ? RISC_CLOCK_RATE_NTSC : RISC_CLOCK_RATE_PAL);
190
191         // We divide by 32 here in order to find the frequency of 32 SCLKs in a row (transferring
192         // 16 bits of left data + 16 bits of right data = 32 bits, 1 SCLK = 1 bit transferred).
193         return systemClockFrequency / (32 * (2 * (SCLKFrequencyDivider + 1)));
194 }
195
196 //
197 // LTXD/RTXD/SCLK/SMODE ($F1A148/4C/50/54)
198 //
199 void DACWriteByte(uint32 offset, uint8 data, uint32 who/*= UNKNOWN*/)
200 {
201         WriteLog("DAC: %s writing BYTE %02X at %08X\n", whoName[who], data, offset);
202         if (offset == SCLK + 3)
203                 DACWriteWord(offset - 3, (uint16)data);
204 }
205
206 void DACWriteWord(uint32 offset, uint16 data, uint32 who/*= UNKNOWN*/)
207 {
208         if (offset == LTXD + 2)
209         {
210                 // Spin until buffer has been drained (for too fast processors!)...
211 //Small problem--if Head == 0 and Tail == buffer end, then this will fail... !!! FIX !!!
212 //[DONE]
213                 // Also, we're taking advantage of the fact that the buffer is a multiple of two
214                 // in this check...
215 uint32 spin = 0;
216                 while (((LeftFIFOTailPtr + 2) & (BUFFER_SIZE - 1)) == LeftFIFOHeadPtr)//;
217                 {
218 spin++;
219 //if ((spin & 0x0FFFFFFF) == 0)
220 //      WriteLog("Tail=%X, Head=%X, BUFFER_SIZE-1=%X\n", RightFIFOTailPtr, RightFIFOHeadPtr, BUFFER_SIZE - 1);
221
222 if (spin == 0xFFFF0000)
223 {
224 uint32 ltail = LeftFIFOTailPtr, lhead = LeftFIFOHeadPtr;
225 WriteLog("Tail=%X, Head=%X", ltail, lhead);
226
227         WriteLog("\nStuck in left DAC spinlock! Aborting!\n");
228         WriteLog("LTail=%X, LHead=%X, BUFFER_SIZE-1=%X\n", LeftFIFOTailPtr, LeftFIFOHeadPtr, BUFFER_SIZE - 1);
229         WriteLog("RTail=%X, RHead=%X, BUFFER_SIZE-1=%X\n", RightFIFOTailPtr, RightFIFOHeadPtr, BUFFER_SIZE - 1);
230         WriteLog("From while: Tail=%X, Head=%X", (LeftFIFOTailPtr + 2) & (BUFFER_SIZE - 1), LeftFIFOHeadPtr);
231 //      LogDone();
232 //      exit(0);
233 #warning "Reimplement GUICrashGracefully!"
234 //      GUICrashGracefully("Stuck in left DAC spinlock!");
235         return;
236 }
237                 }//*/
238
239                 SDL_LockAudio();                                                        // Is it necessary to do this? Mebbe.
240                 // We use a circular buffer 'cause it's easy. Note that the callback function
241                 // takes care of dumping audio to the soundcard...! Also note that we're writing
242                 // the samples in the buffer in an interleaved L/R format.
243                 LeftFIFOTailPtr = (LeftFIFOTailPtr + 2) % BUFFER_SIZE;
244                 DACBuffer[LeftFIFOTailPtr] = data;
245                 SDL_UnlockAudio();
246         }
247         else if (offset == RTXD + 2)
248         {
249 /*
250 Here's what's happening now:
251
252 Stuck in right DAC spinlock!
253 Aborting!
254
255 Tail=681, Head=681, BUFFER_SIZE-1=FFFF
256 From while: Tail=683, Head=681
257
258 ????? What the FUCK ?????
259
260 & when I uncomment the lines below spin++; it *doesn't* lock here... WTF?????
261
262 I think it was missing parentheses causing the fuckup... Seems to work now...
263
264 Except for Super Burnout now...! Aarrrgggghhhhh!
265
266 Tail=AC, Head=AE
267 Stuck in left DAC spinlock! Aborting!
268 Tail=AC, Head=AE, BUFFER_SIZE-1=FFFF
269 From while: Tail=AE, Head=AE
270
271 So it's *really* stuck here in the left FIFO. Figure out why!!!
272
273 Prolly 'cause it doesn't set the sample rate right away--betcha it works with the BIOS...
274 It gets farther, but then locks here (weird!):
275
276 Tail=2564, Head=2566
277 Stuck in left DAC spinlock! Aborting!
278 Tail=2564, Head=2566, BUFFER_SIZE-1=FFFF
279 From while: Tail=2566, Head=2566
280
281 Weird--recompile with more WriteLog() entries and it *doesn't* lock...
282 Yeah, because there was no DSP running. Duh!
283
284 Tail=AC, Head=AE
285 Stuck in left DAC spinlock! Aborting!
286 LTail=AC, LHead=AE, BUFFER_SIZE-1=FFFF
287 RTail=AF, RHead=AF, BUFFER_SIZE-1=FFFF
288 From while: Tail=AE, Head=AE
289
290 Odd: The right FIFO is empty, but the left FIFO is full!
291 And this is what is causing the lockup--the DAC callback waits for the side with
292 less samples ready and in this case it's the right channel (that never fills up)
293 that it's waiting for...!
294
295 Okay, with the kludge in place for the right channel not being filled, we select
296 a track and then it locks here:
297
298 Tail=60D8, Head=60DA
299 Stuck in left DAC spinlock! Aborting!
300 LTail=60D8, LHead=60D8, BUFFER_SIZE-1=FFFF
301 RTail=DB, RHead=60D9, BUFFER_SIZE-1=FFFF
302 From while: Tail=60DA, Head=60D8
303 */
304 #warning Spinlock problem--!!! FIX !!!
305 #warning Odd: The right FIFO is empty, but the left FIFO is full!
306                 // Spin until buffer has been drained (for too fast processors!)...
307 uint32 spin = 0;
308                 while (((RightFIFOTailPtr + 2) & (BUFFER_SIZE - 1)) == RightFIFOHeadPtr)//;
309                 {
310 spin++;
311 //if ((spin & 0x0FFFFFFF) == 0)
312 //      WriteLog("Tail=%X, Head=%X, BUFFER_SIZE-1=%X\n", RightFIFOTailPtr, RightFIFOHeadPtr, BUFFER_SIZE - 1);
313
314 if (spin == 0xFFFF0000)
315 {
316 uint32 rtail = RightFIFOTailPtr, rhead = RightFIFOHeadPtr;
317 WriteLog("Tail=%X, Head=%X", rtail, rhead);
318
319         WriteLog("\nStuck in right DAC spinlock! Aborting!\n");
320         WriteLog("LTail=%X, LHead=%X, BUFFER_SIZE-1=%X\n", LeftFIFOTailPtr, LeftFIFOHeadPtr, BUFFER_SIZE - 1);
321         WriteLog("RTail=%X, RHead=%X, BUFFER_SIZE-1=%X\n", RightFIFOTailPtr, RightFIFOHeadPtr, BUFFER_SIZE - 1);
322         WriteLog("From while: Tail=%X, Head=%X", (RightFIFOTailPtr + 2) & (BUFFER_SIZE - 1), RightFIFOHeadPtr);
323 //      LogDone();
324 //      exit(0);
325 #warning "Reimplement GUICrashGracefully!"
326 //      GUICrashGracefully("Stuck in right DAC spinlock!");
327         return;
328 }
329                 }//*/
330
331                 SDL_LockAudio();
332                 RightFIFOTailPtr = (RightFIFOTailPtr + 2) % BUFFER_SIZE;
333                 DACBuffer[RightFIFOTailPtr] = data;
334                 SDL_UnlockAudio();
335 /*#ifdef DEBUG_DAC
336                 else
337                         WriteLog("DAC: Ran into FIFO's right tail pointer!\n");
338 #endif*/
339         }
340         else if (offset == SCLK + 2)                                    // Sample rate
341         {
342                 WriteLog("DAC: Writing %u to SCLK...\n", data);
343                 if ((uint8)data != SCLKFrequencyDivider)
344                 {
345                         SCLKFrequencyDivider = (uint8)data;
346 //Of course a better way would be to query the hardware to find the upper limit...
347                         if (data > 7)   // Anything less than 8 is too high!
348                         {
349                                 if (SDLSoundInitialized)
350                                         SDL_CloseAudio();
351
352                                 desired.freq = GetCalculatedFrequency();// SDL will do conversion on the fly, if it can't get the exact rate. Nice!
353                                 WriteLog("DAC: Changing sample rate to %u Hz!\n", desired.freq);
354
355                                 if (SDLSoundInitialized)
356                                 {
357                                         if (SDL_OpenAudio(&desired, NULL) < 0)  // NULL means SDL guarantees what we want
358                                         {
359 // This is bad, Bad, BAD !!! DON'T ABORT BECAUSE WE DIDN'T GET OUR FREQ! !!! FIX !!!
360 #warning !!! FIX !!! Aborting because of SDL audio problem is bad!
361                                                 WriteLog("DAC: Failed to initialize SDL sound: %s.\nDesired freq: %u\nShutting down!\n", SDL_GetError(), desired.freq);
362 //                                              LogDone();
363 //                                              exit(1);
364 #warning "Reimplement GUICrashGracefully!"
365 //                                              GUICrashGracefully("Failed to initialize SDL sound!");
366                                                 return;
367                                         }
368                                 }
369
370                                 DACReset();
371
372                                 if (SDLSoundInitialized)
373                                         SDL_PauseAudio(false);                  // Start playback!
374                         }
375                 }
376         }
377         else if (offset == SMODE + 2)
378         {
379                 serialMode = data;
380                 WriteLog("DAC: %s writing to SMODE. Bits: %s%s%s%s%s%s [68K PC=%08X]\n", whoName[who],
381                         (data & 0x01 ? "INTERNAL " : ""), (data & 0x02 ? "MODE " : ""),
382                         (data & 0x04 ? "WSEN " : ""), (data & 0x08 ? "RISING " : ""),
383                         (data & 0x10 ? "FALLING " : ""), (data & 0x20 ? "EVERYWORD" : ""),
384                         m68k_get_reg(NULL, M68K_REG_PC));
385         }
386 }
387
388 //
389 // LRXD/RRXD/SSTAT ($F1A148/4C/50)
390 //
391 uint8 DACReadByte(uint32 offset, uint32 who/*= UNKNOWN*/)
392 {
393 //      WriteLog("DAC: %s reading byte from %08X\n", whoName[who], offset);
394         return 0xFF;
395 }
396
397 //static uint16 fakeWord = 0;
398 uint16 DACReadWord(uint32 offset, uint32 who/*= UNKNOWN*/)
399 {
400 //      WriteLog("DAC: %s reading word from %08X\n", whoName[who], offset);
401 //      return 0xFFFF;
402 //      WriteLog("DAC: %s reading WORD %04X from %08X\n", whoName[who], fakeWord, offset);
403 //      return fakeWord++;
404 //NOTE: This only works if a bunch of things are set in BUTCH which we currently don't
405 //      check for. !!! FIX !!!
406 // Partially fixed: We check for I2SCNTRL in the JERRY I2S routine...
407 //      return GetWordFromButchSSI(offset, who);
408         if (offset == LRXD || offset == RRXD)
409                 return 0x0000;
410         else if (offset == LRXD + 2)
411                 return lrxd;
412         else if (offset == RRXD + 2)
413                 return rrxd;
414
415         return 0xFFFF;  // May need SSTAT as well... (but may be a Jaguar II only feature)
416 }