X-Git-Url: http://shamusworld.gotdns.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Fdac.cpp;h=6f9b5a8e57b01d393ec005fae3b2697644311885;hb=ab4f660439ff855171f801e3fdfa3e9de69d991b;hp=be2f2c522a2ca62ce5545d6ad2a0da9271d5637e;hpb=2d57a03aa1aa1f1ee55bfaed762fabe6d6154992;p=virtualjaguar diff --git a/src/dac.cpp b/src/dac.cpp index be2f2c5..6f9b5a8 100644 --- a/src/dac.cpp +++ b/src/dac.cpp @@ -1,219 +1,324 @@ // // 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 +// Rewritten by James Hammons +// (C) 2010 Underground Software +// +// JLH = James Hammons +// +// Who When What +// --- ---------- ------------------------------------------------------------- +// JLH 01/16/2010 Created this log ;-) +// JLH 04/30/2012 Changed SDL audio handler to run JERRY // -#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? + +// 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" -#define BUFFER_SIZE 0x8000 // Make the DAC buffers 32K x 16 bits +#include "SDL.h" +#include "cdrom.h" +#include "dsp.h" +#include "event.h" +#include "jerry.h" +#include "jaguar.h" +#include "log.h" +#include "m68000/m68kinterface.h" +//#include "memory.h" +#include "settings.h" + + +//#define DEBUG_DAC + +#define BUFFER_SIZE 0x10000 // Make the DAC buffers 64K x 16 bits +#define DAC_AUDIO_RATE 48000 // Set the audio rate to 48 KHz // Jaguar memory locations #define LTXD 0xF1A148 #define RTXD 0xF1A14C +#define LRXD 0xF1A148 +#define RRXD 0xF1A14C #define SCLK 0xF1A150 #define SMODE 0xF1A154 -// Local variables +// Global variables -uint32 LeftFIFOHeadPtr, LeftFIFOTailPtr, RightFIFOHeadPtr, RightFIFOTailPtr; -SDL_AudioSpec desired; +// These are defined in memory.h/cpp +//uint16 lrxd, rrxd; // I2S ports (into Jaguar) -// We can get away with using native endian here because we can tell SDL to use the native -// when looking at the sample buffer, i.e., no need to worry about it. +// Local variables -uint16 * DACBuffer; -uint8 SCLKFrequencyDivider = 9; // Start out roughly 44.1K (46164 Hz in NTSC mode) -uint16 serialMode = 0; +static SDL_AudioSpec desired; +static bool SDLSoundInitialized; +static uint8 SCLKFrequencyDivider = 19; // Default is roughly 22 KHz (20774 Hz in NTSC mode) +/*static*/ uint16 serialMode = 0; // Private function prototypes void SDLSoundCallback(void * userdata, Uint8 * buffer, int length); -int GetCalculatedFrequency(void); +void DSPSampleCallback(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"); + SDLSoundInitialized = false; + + if (!vjs.audioEnabled) + { + WriteLog("DAC: Host audio playback disabled.\n"); + return; + } - 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.freq = DAC_AUDIO_RATE; + desired.format = AUDIO_S16SYS; desired.channels = 2; - desired.samples = 4096; // Let's try a 4K buffer (can always go lower) + desired.samples = 2048; // 2K buffer = audio delay of 42.67 ms (@ 48 KHz) desired.callback = SDLSoundCallback; - if (SDL_OpenAudio(&desired, NULL) < 0) // NULL means SDL guarantees what we want + 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. Sample rate: %u\n", desired.freq); } - DACReset(); - SDL_PauseAudio(false); // Start playback! - WriteLog("DAC: Successfully initialized.\n"); + ltxd = lrxd = desired.silence; + + uint32_t riscClockRate = (vjs.hardwareTypeNTSC ? RISC_CLOCK_RATE_NTSC : RISC_CLOCK_RATE_PAL); + uint32_t cyclesPerSample = riscClockRate / DAC_AUDIO_RATE; + WriteLog("DAC: RISC clock = %u, cyclesPerSample = %u\n", riscClockRate, cyclesPerSample); } + // // Reset the sound buffer FIFOs // void DACReset(void) { - LeftFIFOHeadPtr = LeftFIFOTailPtr = 0, RightFIFOHeadPtr = RightFIFOTailPtr = 1; +// LeftFIFOHeadPtr = LeftFIFOTailPtr = 0, RightFIFOHeadPtr = RightFIFOTailPtr = 1; + ltxd = lrxd = desired.silence; } + // -// 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(); + } + 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 // // Note: The samples are packed in the buffer in 16 bit left/16 bit right pairs. +// Also, length is the length of the buffer in BYTES // +static Uint8 * sampleBuffer; +static int bufferIndex = 0; +static int numberOfSamples = 0; +static bool bufferDone = false; void SDLSoundCallback(void * userdata, Uint8 * buffer, int length) { -//WriteLog("DAC: Inside callback...\n"); - if (LeftFIFOHeadPtr != LeftFIFOTailPtr) + // 1st, check to see if the DSP is running. If not, fill the buffer with L/RXTD and exit. + + if (!DSPIsRunning()) + { + for(int i=0; i<(length/2); i+=2) + { + ((uint16_t *)buffer)[i + 0] = ltxd; + ((uint16_t *)buffer)[i + 1] = rtxd; + } + + return; + } + + // The length of time we're dealing with here is 1/48000 s, so we multiply this + // by the number of cycles per second to get the number of cycles for one sample. + uint32_t riscClockRate = (vjs.hardwareTypeNTSC ? RISC_CLOCK_RATE_NTSC : RISC_CLOCK_RATE_PAL); + uint32_t cyclesPerSample = riscClockRate / DAC_AUDIO_RATE; + // This is the length of time +// timePerSample = (1000000.0 / (double)riscClockRate) * (); + + // Now, run the DSP for that length of time for each sample we need to make + + bufferIndex = 0; + sampleBuffer = buffer; + numberOfSamples = length / 2; + bufferDone = false; + + SetCallbackTime(DSPSampleCallback, 1000000.0 / (double)DAC_AUDIO_RATE, EVENT_JERRY); + + // These timings are tied to NTSC, need to fix that in event.cpp/h! + do + { + double timeToNextEvent = GetTimeToNextEvent(EVENT_JERRY); + + if (vjs.DSPEnabled) + { + if (vjs.usePipelinedDSP) + DSPExecP2(USEC_TO_RISC_CYCLES(timeToNextEvent)); + else + DSPExec(USEC_TO_RISC_CYCLES(timeToNextEvent)); + } + + HandleNextEvent(EVENT_JERRY); + } + while (!bufferDone); +} + + +void DSPSampleCallback(void) +{ + ((uint16_t *)sampleBuffer)[bufferIndex + 0] = ltxd; + ((uint16_t *)sampleBuffer)[bufferIndex + 1] = rtxd; + bufferIndex += 2; + + if (bufferIndex == numberOfSamples) { -//WriteLog("DAC: About to write some data!\n"); - int numLeftSamplesReady - = (LeftFIFOTailPtr + (LeftFIFOTailPtr < LeftFIFOHeadPtr ? BUFFER_SIZE : 0)) - - LeftFIFOHeadPtr; - int numRightSamplesReady - = (RightFIFOTailPtr + (RightFIFOTailPtr < RightFIFOHeadPtr ? BUFFER_SIZE : 0)) - - RightFIFOHeadPtr; - int numSamplesReady - = (numLeftSamplesReady < numRightSamplesReady - ? numLeftSamplesReady : numRightSamplesReady) * 2; - - if (numSamplesReady > length) - numSamplesReady = 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