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