X-Git-Url: http://shamusworld.gotdns.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Fdac.cpp;h=4cf8da2d94710468c75083282007ec7ebc977c25;hb=b79146c85ed8f85acc80fe56534f72cd777f0b02;hp=8c60b5ff710771c3b83c0801e1b9c32e9d4e78b2;hpb=19197c6b601dbfd415ded7ac4918813e1805ca39;p=virtualjaguar diff --git a/src/dac.cpp b/src/dac.cpp index 8c60b5f..4cf8da2 100644 --- a/src/dac.cpp +++ b/src/dac.cpp @@ -1,37 +1,70 @@ // // DAC (really, Synchronous Serial Interface) Handler // -// by cal2 +// Originally by David Raingeard // GCC/SDL port by Niels Wagenaar (Linux/WIN32) and Caz (BeOS) // Rewritten by James L. Hammons +// (C) 2010 Underground Software +// +// JLH = James L. Hammons +// +// Who When What +// --- ---------- ------------------------------------------------------------- +// JLH 01/16/2010 Created this log ;-) // -#include -#include "jaguar.h" +// Need to set up defaults that the BIOS sets for the SSI here in DACInit()... !!! FIX !!! +// or something like that... Seems like it already does, but it doesn't seem to +// work correctly...! Perhaps just need to set up SSI stuff so BUTCH doesn't get +// confused... + +// ALSO: Need to implement some form of proper locking to replace the clusterfuck +// that is the current spinlock implementation. Since the DSP is a separate +// entity, could we get away with running it in the sound IRQ? + +// ALSO: It may be a good idea to physically separate the left and right buffers +// to prevent things like the DSP filling only one side and such. Do such +// mono modes exist on the Jag? Seems to according to Super Burnout. + #include "dac.h" +#include "SDL.h" +//#include "gui.h" +#include "jaguar.h" +#include "log.h" +#include "m68k.h" +//#include "memory.h" +#include "settings.h" + //#define DEBUG_DAC -#define BUFFER_SIZE 0x8000 // Make the DAC buffers 32K x 16 bits +#define BUFFER_SIZE 0x10000 // Make the DAC buffers 64K x 16 bits // Jaguar memory locations #define LTXD 0xF1A148 #define RTXD 0xF1A14C +#define LRXD 0xF1A148 +#define RRXD 0xF1A14C #define SCLK 0xF1A150 #define SMODE 0xF1A154 +// Global variables + +uint16 lrxd, rrxd; // I2S ports (into Jaguar) + // Local variables -uint32 LeftFIFOHeadPtr, LeftFIFOTailPtr, RightFIFOHeadPtr, RightFIFOTailPtr; -SDL_AudioSpec desired; +static uint32 LeftFIFOHeadPtr, LeftFIFOTailPtr, RightFIFOHeadPtr, RightFIFOTailPtr; +static SDL_AudioSpec desired; +static bool SDLSoundInitialized = false; // We can get away with using native endian here because we can tell SDL to use the native // endian when looking at the sample buffer, i.e., no need to worry about it. -uint16 * DACBuffer; -uint8 SCLKFrequencyDivider = 19; // Default is roughly 22 KHz (20774 Hz in NTSC mode) -uint16 serialMode = 0; +static uint16 DACBuffer[BUFFER_SIZE]; +static uint8 SCLKFrequencyDivider = 19; // Default is roughly 22 KHz (20774 Hz in NTSC mode) +/*static*/ uint16 serialMode = 0; // Private function prototypes @@ -39,28 +72,29 @@ void SDLSoundCallback(void * userdata, Uint8 * buffer, int length); int GetCalculatedFrequency(void); // -// Initialize the SDL sound system (?) (!) +// Initialize the SDL sound system // void DACInit(void) { - memory_malloc_secure((void **)&DACBuffer, BUFFER_SIZE * sizeof(uint16), "DAC buffer"); +// memory_malloc_secure((void **)&DACBuffer, BUFFER_SIZE * sizeof(uint16), "DAC buffer"); +// DACBuffer = (uint16 *)memory_malloc(BUFFER_SIZE * sizeof(uint16), "DAC buffer"); desired.freq = GetCalculatedFrequency(); // SDL will do conversion on the fly, if it can't get the exact rate. Nice! desired.format = AUDIO_S16SYS; // This uses the native endian (for portability)... desired.channels = 2; - desired.samples = 4096; // Let's try a 4K buffer (can always go lower) +// desired.samples = 4096; // Let's try a 4K buffer (can always go lower) + desired.samples = 2048; // Let's try a 2K buffer (can always go lower) desired.callback = SDLSoundCallback; if (SDL_OpenAudio(&desired, NULL) < 0) // NULL means SDL guarantees what we want + WriteLog("DAC: Failed to initialize SDL sound...\n"); + else { - WriteLog("DAC: Failed to initialize SDL sound. Shutting down!\n"); - log_done(); - exit(1); + SDLSoundInitialized = true; + DACReset(); + SDL_PauseAudio(false); // Start playback! + WriteLog("DAC: Successfully initialized.\n"); } - - DACReset(); - SDL_PauseAudio(false); // Start playback! - WriteLog("DAC: Successfully initialized.\n"); } // @@ -72,12 +106,17 @@ void DACReset(void) } // -// Close down the SDL sound subsystem (?) (!) +// Close down the SDL sound subsystem // void DACDone(void) { - SDL_PauseAudio(true); - SDL_CloseAudio(); + if (SDLSoundInitialized) + { + SDL_PauseAudio(true); + SDL_CloseAudio(); + } + +// memory_free(DACBuffer); WriteLog("DAC: Done.\n"); } @@ -89,6 +128,7 @@ void DACDone(void) void SDLSoundCallback(void * userdata, Uint8 * buffer, int length) { // Clear the buffer to silence, in case the DAC buffer is empty (or short) +//This causes choppy sound... Ick. memset(buffer, desired.silence, length); //WriteLog("DAC: Inside callback...\n"); if (LeftFIFOHeadPtr != LeftFIFOTailPtr) @@ -100,10 +140,16 @@ void SDLSoundCallback(void * userdata, Uint8 * buffer, int length) int numRightSamplesReady = (RightFIFOTailPtr + (RightFIFOTailPtr < RightFIFOHeadPtr ? BUFFER_SIZE : 0)) - RightFIFOHeadPtr; +//This waits for the slower side to catch up. If writing only one side, then this +//causes the buffer not to drain... int numSamplesReady = (numLeftSamplesReady < numRightSamplesReady ? numLeftSamplesReady : numRightSamplesReady);//Hmm. * 2; +//Kludge, until I can figure out WTF is going on WRT Super Burnout. +if (numLeftSamplesReady == 0 || numRightSamplesReady == 0) + numSamplesReady = numLeftSamplesReady + numRightSamplesReady; + //The numbers look good--it's just that the DSP can't get enough samples in the DAC buffer! //WriteLog("DAC: Left/RightFIFOHeadPtr: %u/%u, Left/RightFIFOTailPtr: %u/%u\n", LeftFIFOHeadPtr, RightFIFOHeadPtr, LeftFIFOTailPtr, RightFIFOTailPtr); //WriteLog(" numLeft/RightSamplesReady: %i/%i, numSamplesReady: %i, length of buffer: %i\n", numLeftSamplesReady, numRightSamplesReady, numSamplesReady, length); @@ -119,16 +165,16 @@ void SDLSoundCallback(void * userdata, Uint8 * buffer, int length) // Actually, it's a bit more involved than this, but this is the general idea: // memcpy(buffer, DACBuffer, length); for(int i=0; i Left/RightFIFOHeadPtr: %u/%u, Left/RightFIFOTailPtr: %u/%u\n", LeftFIFOHeadPtr, RightFIFOHeadPtr, LeftFIFOTailPtr, RightFIFOTailPtr); +// LeftFIFOHeadPtr = (LeftFIFOHeadPtr + numSamplesReady) & (BUFFER_SIZE - 1); +// RightFIFOHeadPtr = (RightFIFOHeadPtr + numSamplesReady) & (BUFFER_SIZE - 1); +//WriteLog(" -> Left/RightFIFOHeadPtr: %04X/%04X, Left/RightFIFOTailPtr: %04X/%04X\n", LeftFIFOHeadPtr, RightFIFOHeadPtr, LeftFIFOTailPtr, RightFIFOTailPtr); } //Hmm. Seems that the SDL buffer isn't being starved by the DAC buffer... // else @@ -136,12 +182,11 @@ void SDLSoundCallback(void * userdata, Uint8 * buffer, int length) } // -// Calculate the frequency of SCLK * 32 using the divider +// Calculate the freq9uency of SCLK * 32 using the divider // int GetCalculatedFrequency(void) { - extern bool hardwareTypeNTSC; - int systemClockFrequency = (hardwareTypeNTSC ? RISC_CLOCK_RATE_NTSC : RISC_CLOCK_RATE_PAL); + int systemClockFrequency = (vjs.hardwareTypeNTSC ? RISC_CLOCK_RATE_NTSC : RISC_CLOCK_RATE_PAL); // We divide by 32 here in order to find the frequency of 32 SCLKs in a row (transferring // 16 bits of left data + 16 bits of right data = 32 bits, 1 SCLK = 1 bit transferred). @@ -151,52 +196,146 @@ int GetCalculatedFrequency(void) // // LTXD/RTXD/SCLK/SMODE ($F1A148/4C/50/54) // -void DACWriteByte(uint32 offset, uint8 data) +void DACWriteByte(uint32 offset, uint8 data, uint32 who/*= UNKNOWN*/) { - WriteLog("DAC: Writing %02X at %08X\n", data, offset); + WriteLog("DAC: %s writing BYTE %02X at %08X\n", whoName[who], data, offset); if (offset == SCLK + 3) DACWriteWord(offset - 3, (uint16)data); } -void DACWriteWord(uint32 offset, uint16 data) +void DACWriteWord(uint32 offset, uint16 data, uint32 who/*= UNKNOWN*/) { if (offset == LTXD + 2) { - if (LeftFIFOTailPtr + 2 != LeftFIFOHeadPtr) + // Spin until buffer has been drained (for too fast processors!)... +//Small problem--if Head == 0 and Tail == buffer end, then this will fail... !!! FIX !!! +//[DONE] + // Also, we're taking advantage of the fact that the buffer is a multiple of two + // in this check... +uint32 spin = 0; + while (((LeftFIFOTailPtr + 2) & (BUFFER_SIZE - 1)) == LeftFIFOHeadPtr)//; { - SDL_LockAudio(); // Is it necessary to do this? Mebbe. - // We use a circular buffer 'cause it's easy. Note that the callback function - // takes care of dumping audio to the soundcard...! Also note that we're writing - // the samples in the buffer in an interleaved L/R format. - LeftFIFOTailPtr = (LeftFIFOTailPtr + 2) % BUFFER_SIZE; - DACBuffer[LeftFIFOTailPtr] = data; -// Aaron's code does this, but I don't know why... -//Flipping this bit makes the audio MUCH louder. Need to look at the amplitude of the -//waveform to see if any massaging is needed here... -//Looks like a cheap & dirty way to convert signed samples to unsigned... -// DACBuffer[LeftFIFOTailPtr] = data ^ 0x8000; - SDL_UnlockAudio(); - } -#ifdef DEBUG_DAC - else - WriteLog("DAC: Ran into FIFO's left tail pointer!\n"); -#endif +spin++; +//if ((spin & 0x0FFFFFFF) == 0) +// WriteLog("Tail=%X, Head=%X, BUFFER_SIZE-1=%X\n", RightFIFOTailPtr, RightFIFOHeadPtr, BUFFER_SIZE - 1); + +if (spin == 0xFFFF0000) +{ +uint32 ltail = LeftFIFOTailPtr, lhead = LeftFIFOHeadPtr; +WriteLog("Tail=%X, Head=%X", ltail, lhead); + + WriteLog("\nStuck in left DAC spinlock! Aborting!\n"); + WriteLog("LTail=%X, LHead=%X, BUFFER_SIZE-1=%X\n", LeftFIFOTailPtr, LeftFIFOHeadPtr, BUFFER_SIZE - 1); + WriteLog("RTail=%X, RHead=%X, BUFFER_SIZE-1=%X\n", RightFIFOTailPtr, RightFIFOHeadPtr, BUFFER_SIZE - 1); + WriteLog("From while: Tail=%X, Head=%X", (LeftFIFOTailPtr + 2) & (BUFFER_SIZE - 1), LeftFIFOHeadPtr); +// LogDone(); +// exit(0); +#warning "Reimplement GUICrashGracefully!" +// GUICrashGracefully("Stuck in left DAC spinlock!"); + return; +} + }//*/ + + SDL_LockAudio(); // Is it necessary to do this? Mebbe. + // We use a circular buffer 'cause it's easy. Note that the callback function + // takes care of dumping audio to the soundcard...! Also note that we're writing + // the samples in the buffer in an interleaved L/R format. + LeftFIFOTailPtr = (LeftFIFOTailPtr + 2) % BUFFER_SIZE; + DACBuffer[LeftFIFOTailPtr] = data; + SDL_UnlockAudio(); } else if (offset == RTXD + 2) { - if (RightFIFOTailPtr + 2 != RightFIFOHeadPtr) +/* +Here's what's happening now: + +Stuck in right DAC spinlock! +Aborting! + +Tail=681, Head=681, BUFFER_SIZE-1=FFFF +From while: Tail=683, Head=681 + +????? What the FUCK ????? + +& when I uncomment the lines below spin++; it *doesn't* lock here... WTF????? + +I think it was missing parentheses causing the fuckup... Seems to work now... + +Except for Super Burnout now...! Aarrrgggghhhhh! + +Tail=AC, Head=AE +Stuck in left DAC spinlock! Aborting! +Tail=AC, Head=AE, BUFFER_SIZE-1=FFFF +From while: Tail=AE, Head=AE + +So it's *really* stuck here in the left FIFO. Figure out why!!! + +Prolly 'cause it doesn't set the sample rate right away--betcha it works with the BIOS... +It gets farther, but then locks here (weird!): + +Tail=2564, Head=2566 +Stuck in left DAC spinlock! Aborting! +Tail=2564, Head=2566, BUFFER_SIZE-1=FFFF +From while: Tail=2566, Head=2566 + +Weird--recompile with more WriteLog() entries and it *doesn't* lock... +Yeah, because there was no DSP running. Duh! + +Tail=AC, Head=AE +Stuck in left DAC spinlock! Aborting! +LTail=AC, LHead=AE, BUFFER_SIZE-1=FFFF +RTail=AF, RHead=AF, BUFFER_SIZE-1=FFFF +From while: Tail=AE, Head=AE + +Odd: The right FIFO is empty, but the left FIFO is full! +And this is what is causing the lockup--the DAC callback waits for the side with +less samples ready and in this case it's the right channel (that never fills up) +that it's waiting for...! + +Okay, with the kludge in place for the right channel not being filled, we select +a track and then it locks here: + +Tail=60D8, Head=60DA +Stuck in left DAC spinlock! Aborting! +LTail=60D8, LHead=60D8, BUFFER_SIZE-1=FFFF +RTail=DB, RHead=60D9, BUFFER_SIZE-1=FFFF +From while: Tail=60DA, Head=60D8 +*/ +#warning Spinlock problem--!!! FIX !!! +#warning Odd: The right FIFO is empty, but the left FIFO is full! + // Spin until buffer has been drained (for too fast processors!)... +uint32 spin = 0; + while (((RightFIFOTailPtr + 2) & (BUFFER_SIZE - 1)) == RightFIFOHeadPtr)//; { - SDL_LockAudio(); - RightFIFOTailPtr = (RightFIFOTailPtr + 2) % BUFFER_SIZE; - DACBuffer[RightFIFOTailPtr] = data; -// Aaron's code does this, but I don't know why... -// DACBuffer[RightFIFOTailPtr] = data ^ 0x8000; - SDL_UnlockAudio(); - } -#ifdef DEBUG_DAC +spin++; +//if ((spin & 0x0FFFFFFF) == 0) +// WriteLog("Tail=%X, Head=%X, BUFFER_SIZE-1=%X\n", RightFIFOTailPtr, RightFIFOHeadPtr, BUFFER_SIZE - 1); + +if (spin == 0xFFFF0000) +{ +uint32 rtail = RightFIFOTailPtr, rhead = RightFIFOHeadPtr; +WriteLog("Tail=%X, Head=%X", rtail, rhead); + + WriteLog("\nStuck in right DAC spinlock! Aborting!\n"); + WriteLog("LTail=%X, LHead=%X, BUFFER_SIZE-1=%X\n", LeftFIFOTailPtr, LeftFIFOHeadPtr, BUFFER_SIZE - 1); + WriteLog("RTail=%X, RHead=%X, BUFFER_SIZE-1=%X\n", RightFIFOTailPtr, RightFIFOHeadPtr, BUFFER_SIZE - 1); + WriteLog("From while: Tail=%X, Head=%X", (RightFIFOTailPtr + 2) & (BUFFER_SIZE - 1), RightFIFOHeadPtr); +// LogDone(); +// exit(0); +#warning "Reimplement GUICrashGracefully!" +// GUICrashGracefully("Stuck in right DAC spinlock!"); + return; +} + }//*/ + + SDL_LockAudio(); + RightFIFOTailPtr = (RightFIFOTailPtr + 2) % BUFFER_SIZE; + DACBuffer[RightFIFOTailPtr] = data; + SDL_UnlockAudio(); +/*#ifdef DEBUG_DAC else WriteLog("DAC: Ran into FIFO's right tail pointer!\n"); -#endif +#endif*/ } else if (offset == SCLK + 2) // Sample rate { @@ -207,43 +346,71 @@ void DACWriteWord(uint32 offset, uint16 data) //Of course a better way would be to query the hardware to find the upper limit... if (data > 7) // Anything less than 8 is too high! { - SDL_CloseAudio(); + if (SDLSoundInitialized) + SDL_CloseAudio(); + desired.freq = GetCalculatedFrequency();// SDL will do conversion on the fly, if it can't get the exact rate. Nice! WriteLog("DAC: Changing sample rate to %u Hz!\n", desired.freq); - if (SDL_OpenAudio(&desired, NULL) < 0) // NULL means SDL guarantees what we want + if (SDLSoundInitialized) { - WriteLog("DAC: Failed to initialize SDL sound: %s.\nDesired freq: %u\nShutting down!\n", SDL_GetError(), desired.freq); - log_done(); - exit(1); + if (SDL_OpenAudio(&desired, NULL) < 0) // NULL means SDL guarantees what we want + { +// This is bad, Bad, BAD !!! DON'T ABORT BECAUSE WE DIDN'T GET OUR FREQ! !!! FIX !!! +#warning !!! FIX !!! Aborting because of SDL audio problem is bad! + WriteLog("DAC: Failed to initialize SDL sound: %s.\nDesired freq: %u\nShutting down!\n", SDL_GetError(), desired.freq); +// LogDone(); +// exit(1); +#warning "Reimplement GUICrashGracefully!" +// GUICrashGracefully("Failed to initialize SDL sound!"); + return; + } } DACReset(); - SDL_PauseAudio(false); // Start playback! + + if (SDLSoundInitialized) + SDL_PauseAudio(false); // Start playback! } } } else if (offset == SMODE + 2) { serialMode = data; - WriteLog("DAC: Writing to SMODE. Bits: %s%s%s%s%s%s\n", + WriteLog("DAC: %s writing to SMODE. Bits: %s%s%s%s%s%s [68K PC=%08X]\n", whoName[who], (data & 0x01 ? "INTERNAL " : ""), (data & 0x02 ? "MODE " : ""), (data & 0x04 ? "WSEN " : ""), (data & 0x08 ? "RISING " : ""), - (data & 0x10 ? "FALLING " : ""), (data & 0x20 ? "EVERYWORD" : "")); + (data & 0x10 ? "FALLING " : ""), (data & 0x20 ? "EVERYWORD" : ""), + m68k_get_reg(NULL, M68K_REG_PC)); } } // // LRXD/RRXD/SSTAT ($F1A148/4C/50) // -uint8 DACReadByte(uint32 offset) +uint8 DACReadByte(uint32 offset, uint32 who/*= UNKNOWN*/) { -// WriteLog("DAC: Reading byte from %08X\n", offset); +// WriteLog("DAC: %s reading byte from %08X\n", whoName[who], offset); return 0xFF; } -uint16 DACReadWord(uint32 offset) +//static uint16 fakeWord = 0; +uint16 DACReadWord(uint32 offset, uint32 who/*= UNKNOWN*/) { -// WriteLog("DAC: Reading word from %08X\n", offset); - return 0xFFFF; +// WriteLog("DAC: %s reading word from %08X\n", whoName[who], offset); +// return 0xFFFF; +// WriteLog("DAC: %s reading WORD %04X from %08X\n", whoName[who], fakeWord, offset); +// return fakeWord++; +//NOTE: This only works if a bunch of things are set in BUTCH which we currently don't +// check for. !!! FIX !!! +// Partially fixed: We check for I2SCNTRL in the JERRY I2S routine... +// return GetWordFromButchSSI(offset, who); + if (offset == LRXD || offset == RRXD) + return 0x0000; + else if (offset == LRXD + 2) + return lrxd; + else if (offset == RRXD + 2) + return rrxd; + + return 0xFFFF; // May need SSTAT as well... (but may be a Jaguar II only feature) }