X-Git-Url: http://shamusworld.gotdns.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Fdac.cpp;h=983680791fccefe73613246120633f4bf8543e95;hb=19cb30261693d5c56c79d87030cfe8e1dc9ca033;hp=4cf8da2d94710468c75083282007ec7ebc977c25;hpb=94e1e961b57f253b760298ab0bae96a7de6d20fa;p=virtualjaguar diff --git a/src/dac.cpp b/src/dac.cpp index 4cf8da2..9836807 100644 --- a/src/dac.cpp +++ b/src/dac.cpp @@ -3,10 +3,10 @@ // // Originally by David Raingeard // GCC/SDL port by Niels Wagenaar (Linux/WIN32) and Caz (BeOS) -// Rewritten by James L. Hammons +// Rewritten by James Hammons // (C) 2010 Underground Software // -// JLH = James L. Hammons +// JLH = James Hammons // // Who When What // --- ---------- ------------------------------------------------------------- @@ -26,6 +26,27 @@ // 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. +// After testing on a real Jaguar, it seems clear that the I2S interrupt drives +// the audio subsystem. So while you can drive the audio at a *slower* rate than +// set by SCLK, you can't drive it any *faster*. Also note, that if the I2S +// interrupt is not enabled/running on the DSP, then there is no audio. Also, +// audio can be muted by clearing bit 8 of JOYSTICK (JOY1). +// +// Approach: We can run the DSP in the host system's audio IRQ, by running the +// DSP for the alloted time (depending on the host buffer size & sample rate) +// by simply reading the L/R_I2S (L/RTXD) registers at regular intervals. We +// would also have to time the I2S/TIMER0/TIMER1 interrupts in the DSP as well. +// This way, we can run the host audio IRQ at, say, 48 KHz and not have to care +// so much about SCLK and running a separate buffer and all the attendant +// garbage that comes with that awful approach. +// +// There would still be potential gotchas, as the SCLK can theoretically drive +// the I2S at 26590906 / 2 (for SCLK == 0) = 13.3 MHz which corresponds to an +// audio rate 416 KHz (dividing the I2S rate by 32, for 16-bit stereo). It +// seems doubtful that anything useful could come of such a high rate, and we +// can probably safely ignore any such ridiculously high audio rates. It won't +// sound the same as on a real Jaguar, but who cares? :-) + #include "dac.h" #include "SDL.h" @@ -51,13 +72,13 @@ // Global variables -uint16 lrxd, rrxd; // I2S ports (into Jaguar) +//uint16 lrxd, rrxd; // I2S ports (into Jaguar) // Local variables static uint32 LeftFIFOHeadPtr, LeftFIFOTailPtr, RightFIFOHeadPtr, RightFIFOTailPtr; static SDL_AudioSpec desired; -static bool SDLSoundInitialized = false; +static bool SDLSoundInitialized; // 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. @@ -69,13 +90,20 @@ static uint8 SCLKFrequencyDivider = 19; // Default is roughly 22 KHz (20774 H // Private function prototypes void SDLSoundCallback(void * userdata, Uint8 * buffer, int length); -int GetCalculatedFrequency(void); // // Initialize the SDL sound system // void DACInit(void) { + SDLSoundInitialized = false; + + if (!vjs.audioEnabled) + { + WriteLog("DAC: Host audio playback disabled.\n"); + return; + } + // memory_malloc_secure((void **)&DACBuffer, BUFFER_SIZE * sizeof(uint16), "DAC buffer"); // DACBuffer = (uint16 *)memory_malloc(BUFFER_SIZE * sizeof(uint16), "DAC buffer"); @@ -120,6 +148,16 @@ void DACDone(void) WriteLog("DAC: Done.\n"); } + +// Approach: Run the DSP for however many cycles needed to correspond to whatever sample rate +// we've set the audio to run at. So, e.g., if we run it at 48 KHz, then we would run the DSP +// for however much time it takes to fill the buffer. So with a 2K buffer, this would correspond +// to running the DSP for 0.042666... seconds. At 26590906 Hz, this would correspond to +// running the DSP for 1134545 cycles. You would then sample the L/RTXD registers every +// 1134545 / 2048 = 554 cycles to fill the buffer. You would also have to manage interrupt +// timing as well (generating them at the proper times), but that shouldn't be too difficult... +// If the DSP isn't running, then fill the buffer with L/RTXD and exit. + // // SDL callback routine to fill audio buffer // @@ -182,7 +220,7 @@ if (numLeftSamplesReady == 0 || numRightSamplesReady == 0) } // -// Calculate the freq9uency of SCLK * 32 using the divider +// Calculate the frequency of SCLK * 32 using the divider // int GetCalculatedFrequency(void) { @@ -193,6 +231,44 @@ int GetCalculatedFrequency(void) return systemClockFrequency / (32 * (2 * (SCLKFrequencyDivider + 1))); } +static int oldFreq = 0; + +void DACSetNewFrequency(int freq) +{ + if (freq == oldFreq) + return; + + oldFreq = freq; + + // Should do some sanity checking on the frequency... + + if (SDLSoundInitialized) + SDL_CloseAudio(); + + desired.freq = freq;// 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 (SDLSoundInitialized) + { + 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(); + + if (SDLSoundInitialized) + SDL_PauseAudio(false); // Start playback! +} + // // LTXD/RTXD/SCLK/SMODE ($F1A148/4C/50/54) // @@ -207,6 +283,8 @@ void DACWriteWord(uint32 offset, uint16 data, uint32 who/*= UNKNOWN*/) { if (offset == LTXD + 2) { + if (!SDLSoundInitialized) + return; // 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] @@ -246,6 +324,8 @@ WriteLog("Tail=%X, Head=%X", ltail, lhead); } else if (offset == RTXD + 2) { + if (!SDLSoundInitialized) + return; /* Here's what's happening now: