Commit | Line | Data |
---|---|---|
cf76e892 JPM |
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 | // JLH 04/30/2012 Changed SDL audio handler to run JERRY | |
15 | // | |
16 | ||
17 | // Need to set up defaults that the BIOS sets for the SSI here in DACInit()... !!! FIX !!! | |
18 | // or something like that... Seems like it already does, but it doesn't seem to | |
19 | // work correctly...! Perhaps just need to set up SSI stuff so BUTCH doesn't get | |
20 | // confused... | |
21 | ||
22 | // After testing on a real Jaguar, it seems clear that the I2S interrupt drives | |
23 | // the audio subsystem. So while you can drive the audio at a *slower* rate than | |
24 | // set by SCLK, you can't drive it any *faster*. Also note, that if the I2S | |
25 | // interrupt is not enabled/running on the DSP, then there is no audio. Also, | |
26 | // audio can be muted by clearing bit 8 of JOYSTICK (JOY1). | |
27 | // | |
28 | // Approach: We can run the DSP in the host system's audio IRQ, by running the | |
29 | // DSP for the alloted time (depending on the host buffer size & sample rate) | |
30 | // by simply reading the L/R_I2S (L/RTXD) registers at regular intervals. We | |
31 | // would also have to time the I2S/TIMER0/TIMER1 interrupts in the DSP as well. | |
32 | // This way, we can run the host audio IRQ at, say, 48 KHz and not have to care | |
33 | // so much about SCLK and running a separate buffer and all the attendant | |
34 | // garbage that comes with that awful approach. | |
35 | // | |
36 | // There would still be potential gotchas, as the SCLK can theoretically drive | |
37 | // the I2S at 26590906 / 2 (for SCLK == 0) = 13.3 MHz which corresponds to an | |
38 | // audio rate 416 KHz (dividing the I2S rate by 32, for 16-bit stereo). It | |
39 | // seems doubtful that anything useful could come of such a high rate, and we | |
40 | // can probably safely ignore any such ridiculously high audio rates. It won't | |
41 | // sound the same as on a real Jaguar, but who cares? :-) | |
42 | ||
43 | #include "dac.h" | |
44 | ||
45 | #include "SDL.h" | |
46 | #include "cdrom.h" | |
47 | #include "dsp.h" | |
48 | #include "event.h" | |
49 | #include "jerry.h" | |
50 | #include "jaguar.h" | |
51 | #include "log.h" | |
52 | #include "m68000/m68kinterface.h" | |
53 | //#include "memory.h" | |
54 | #include "settings.h" | |
55 | ||
56 | ||
57 | //#define DEBUG_DAC | |
58 | ||
59 | #define BUFFER_SIZE 0x10000 // Make the DAC buffers 64K x 16 bits | |
60 | #define DAC_AUDIO_RATE 48000 // Set the audio rate to 48 KHz | |
61 | ||
62 | // Jaguar memory locations | |
63 | ||
64 | #define LTXD 0xF1A148 | |
65 | #define RTXD 0xF1A14C | |
66 | #define LRXD 0xF1A148 | |
67 | #define RRXD 0xF1A14C | |
68 | #define SCLK 0xF1A150 | |
69 | #define SMODE 0xF1A154 | |
70 | ||
71 | // Global variables | |
72 | ||
73 | // These are defined in memory.h/cpp | |
74 | //uint16_t lrxd, rrxd; // I2S ports (into Jaguar) | |
75 | ||
76 | // Local variables | |
77 | ||
78 | static SDL_AudioSpec desired; | |
79 | static bool SDLSoundInitialized; | |
80 | //static uint8_t SCLKFrequencyDivider = 19; // Default is roughly 22 KHz (20774 Hz in NTSC mode) | |
81 | // /*static*/ uint16_t serialMode = 0; | |
82 | ||
83 | // Private function prototypes | |
84 | ||
85 | void SDLSoundCallback(void * userdata, Uint8 * buffer, int length); | |
86 | void DSPSampleCallback(void); | |
87 | ||
88 | ||
89 | // | |
90 | // Initialize the SDL sound system | |
91 | // | |
92 | void DACInit(void) | |
93 | { | |
94 | SDLSoundInitialized = false; | |
95 | ||
96 | // if (!vjs.audioEnabled) | |
97 | if (!vjs.DSPEnabled) | |
98 | { | |
99 | WriteLog("DAC: DSP/host audio playback disabled.\n"); | |
100 | return; | |
101 | } | |
102 | ||
103 | desired.freq = DAC_AUDIO_RATE; | |
104 | desired.format = AUDIO_S16SYS; | |
105 | desired.channels = 2; | |
106 | desired.samples = 2048; // 2K buffer = audio delay of 42.67 ms (@ 48 KHz) | |
107 | desired.callback = SDLSoundCallback; | |
108 | ||
109 | if (SDL_OpenAudio(&desired, NULL) < 0) // NULL means SDL guarantees what we want | |
110 | WriteLog("DAC: Failed to initialize SDL sound...\n"); | |
111 | else | |
112 | { | |
113 | SDLSoundInitialized = true; | |
114 | DACReset(); | |
115 | SDL_PauseAudio(false); // Start playback! | |
116 | WriteLog("DAC: Successfully initialized. Sample rate: %u\n", desired.freq); | |
117 | } | |
118 | ||
119 | ltxd = lrxd = desired.silence; | |
120 | sclk = 19; // Default is roughly 22 KHz | |
121 | ||
122 | uint32_t riscClockRate = (vjs.hardwareTypeNTSC ? RISC_CLOCK_RATE_NTSC : RISC_CLOCK_RATE_PAL); | |
123 | uint32_t cyclesPerSample = riscClockRate / DAC_AUDIO_RATE; | |
124 | WriteLog("DAC: RISC clock = %u, cyclesPerSample = %u\n", riscClockRate, cyclesPerSample); | |
125 | } | |
126 | ||
127 | ||
128 | // | |
129 | // Reset the sound buffer FIFOs | |
130 | // | |
131 | void DACReset(void) | |
132 | { | |
133 | // LeftFIFOHeadPtr = LeftFIFOTailPtr = 0, RightFIFOHeadPtr = RightFIFOTailPtr = 1; | |
134 | ltxd = lrxd = desired.silence; | |
135 | } | |
136 | ||
137 | ||
138 | // | |
139 | // Pause/unpause the SDL audio thread | |
140 | // | |
141 | void DACPauseAudioThread(bool state/*= true*/) | |
142 | { | |
143 | SDL_PauseAudio(state); | |
144 | } | |
145 | ||
146 | ||
147 | // | |
148 | // Close down the SDL sound subsystem | |
149 | // | |
150 | void DACDone(void) | |
151 | { | |
152 | if (SDLSoundInitialized) | |
153 | { | |
154 | SDL_PauseAudio(true); | |
155 | SDL_CloseAudio(); | |
156 | } | |
157 | ||
158 | WriteLog("DAC: Done.\n"); | |
159 | } | |
160 | ||
161 | ||
162 | // Approach: Run the DSP for however many cycles needed to correspond to whatever sample rate | |
163 | // we've set the audio to run at. So, e.g., if we run it at 48 KHz, then we would run the DSP | |
164 | // for however much time it takes to fill the buffer. So with a 2K buffer, this would correspond | |
165 | // to running the DSP for 0.042666... seconds. At 26590906 Hz, this would correspond to | |
166 | // running the DSP for 1134545 cycles. You would then sample the L/RTXD registers every | |
167 | // 1134545 / 2048 = 554 cycles to fill the buffer. You would also have to manage interrupt | |
168 | // timing as well (generating them at the proper times), but that shouldn't be too difficult... | |
169 | // If the DSP isn't running, then fill the buffer with L/RTXD and exit. | |
170 | ||
171 | // | |
172 | // SDL callback routine to fill audio buffer | |
173 | // | |
174 | // Note: The samples are packed in the buffer in 16 bit left/16 bit right pairs. | |
175 | // Also, length is the length of the buffer in BYTES | |
176 | // | |
177 | static Uint8 * sampleBuffer; | |
178 | static int bufferIndex = 0; | |
179 | static int numberOfSamples = 0; | |
180 | static bool bufferDone = false; | |
181 | void SDLSoundCallback(void * userdata, Uint8 * buffer, int length) | |
182 | { | |
183 | // 1st, check to see if the DSP is running. If not, fill the buffer with L/RXTD and exit. | |
184 | ||
185 | if (!DSPIsRunning()) | |
186 | { | |
187 | for(int i=0; i<(length/2); i+=2) | |
188 | { | |
189 | ((uint16_t *)buffer)[i + 0] = ltxd; | |
190 | ((uint16_t *)buffer)[i + 1] = rtxd; | |
191 | } | |
192 | ||
193 | return; | |
194 | } | |
195 | ||
196 | // The length of time we're dealing with here is 1/48000 s, so we multiply this | |
197 | // by the number of cycles per second to get the number of cycles for one sample. | |
198 | // uint32_t riscClockRate = (vjs.hardwareTypeNTSC ? RISC_CLOCK_RATE_NTSC : RISC_CLOCK_RATE_PAL); | |
199 | // uint32_t cyclesPerSample = riscClockRate / DAC_AUDIO_RATE; | |
200 | // This is the length of time | |
201 | // timePerSample = (1000000.0 / (double)riscClockRate) * (); | |
202 | ||
203 | // Now, run the DSP for that length of time for each sample we need to make | |
204 | ||
205 | bufferIndex = 0; | |
206 | sampleBuffer = buffer; | |
207 | // If length is the length of the sample buffer in BYTES, then shouldn't the # of | |
208 | // samples be / 4? No, because we bump the sample count by 2, so this is OK. | |
209 | numberOfSamples = length / 2; | |
210 | bufferDone = false; | |
211 | ||
212 | SetCallbackTime(DSPSampleCallback, 1000000.0 / (double)DAC_AUDIO_RATE, EVENT_JERRY); | |
213 | ||
214 | // These timings are tied to NTSC, need to fix that in event.cpp/h! [FIXED] | |
215 | do | |
216 | { | |
217 | double timeToNextEvent = GetTimeToNextEvent(EVENT_JERRY); | |
218 | ||
219 | if (vjs.DSPEnabled) | |
220 | { | |
221 | if (vjs.usePipelinedDSP) | |
222 | DSPExecP2(USEC_TO_RISC_CYCLES(timeToNextEvent)); | |
223 | else | |
224 | DSPExec(USEC_TO_RISC_CYCLES(timeToNextEvent)); | |
225 | } | |
226 | ||
227 | HandleNextEvent(EVENT_JERRY); | |
228 | } | |
229 | while (!bufferDone); | |
230 | } | |
231 | ||
232 | ||
233 | void DSPSampleCallback(void) | |
234 | { | |
235 | ((uint16_t *)sampleBuffer)[bufferIndex + 0] = ltxd; | |
236 | ((uint16_t *)sampleBuffer)[bufferIndex + 1] = rtxd; | |
237 | bufferIndex += 2; | |
238 | ||
239 | if (bufferIndex == numberOfSamples) | |
240 | { | |
241 | bufferDone = true; | |
242 | return; | |
243 | } | |
244 | ||
245 | SetCallbackTime(DSPSampleCallback, 1000000.0 / (double)DAC_AUDIO_RATE, EVENT_JERRY); | |
246 | } | |
247 | ||
248 | ||
249 | #if 0 | |
250 | // | |
251 | // Calculate the frequency of SCLK * 32 using the divider | |
252 | // | |
253 | int GetCalculatedFrequency(void) | |
254 | { | |
255 | int systemClockFrequency = (vjs.hardwareTypeNTSC ? RISC_CLOCK_RATE_NTSC : RISC_CLOCK_RATE_PAL); | |
256 | ||
257 | // We divide by 32 here in order to find the frequency of 32 SCLKs in a row (transferring | |
258 | // 16 bits of left data + 16 bits of right data = 32 bits, 1 SCLK = 1 bit transferred). | |
259 | return systemClockFrequency / (32 * (2 * (SCLKFrequencyDivider + 1))); | |
260 | } | |
261 | #endif | |
262 | ||
263 | ||
264 | // | |
265 | // LTXD/RTXD/SCLK/SMODE ($F1A148/4C/50/54) | |
266 | // | |
267 | void DACWriteByte(uint32_t offset, uint8_t data, uint32_t who/*= UNKNOWN*/) | |
268 | { | |
269 | WriteLog("DAC: %s writing BYTE %02X at %08X\n", whoName[who], data, offset); | |
270 | if (offset == SCLK + 3) | |
271 | DACWriteWord(offset - 3, (uint16_t)data); | |
272 | } | |
273 | ||
274 | ||
275 | void DACWriteWord(uint32_t offset, uint16_t data, uint32_t who/*= UNKNOWN*/) | |
276 | { | |
277 | if (offset == LTXD + 2) | |
278 | { | |
279 | ltxd = data; | |
280 | } | |
281 | else if (offset == RTXD + 2) | |
282 | { | |
283 | rtxd = data; | |
284 | } | |
285 | else if (offset == SCLK + 2) // Sample rate | |
286 | { | |
287 | WriteLog("DAC: Writing %u to SCLK (by %s)...\n", data, whoName[who]); | |
288 | ||
289 | sclk = data & 0xFF; | |
290 | JERRYI2SInterruptTimer = -1; | |
291 | RemoveCallback(JERRYI2SCallback); | |
292 | JERRYI2SCallback(); | |
293 | } | |
294 | else if (offset == SMODE + 2) | |
295 | { | |
296 | // serialMode = data; | |
297 | smode = data; | |
298 | WriteLog("DAC: %s writing to SMODE. Bits: %s%s%s%s%s%s [68K PC=%08X]\n", whoName[who], | |
299 | (data & 0x01 ? "INTERNAL " : ""), (data & 0x02 ? "MODE " : ""), | |
300 | (data & 0x04 ? "WSEN " : ""), (data & 0x08 ? "RISING " : ""), | |
301 | (data & 0x10 ? "FALLING " : ""), (data & 0x20 ? "EVERYWORD" : ""), | |
302 | m68k_get_reg(NULL, M68K_REG_PC)); | |
303 | } | |
304 | } | |
305 | ||
306 | ||
307 | // | |
308 | // LRXD/RRXD/SSTAT ($F1A148/4C/50) | |
309 | // | |
310 | uint8_t DACReadByte(uint32_t offset, uint32_t who/*= UNKNOWN*/) | |
311 | { | |
312 | // WriteLog("DAC: %s reading byte from %08X\n", whoName[who], offset); | |
313 | return 0xFF; | |
314 | } | |
315 | ||
316 | ||
317 | //static uint16_t fakeWord = 0; | |
318 | uint16_t DACReadWord(uint32_t offset, uint32_t who/*= UNKNOWN*/) | |
319 | { | |
320 | // WriteLog("DAC: %s reading word from %08X\n", whoName[who], offset); | |
321 | // return 0xFFFF; | |
322 | // WriteLog("DAC: %s reading WORD %04X from %08X\n", whoName[who], fakeWord, offset); | |
323 | // return fakeWord++; | |
324 | //NOTE: This only works if a bunch of things are set in BUTCH which we currently don't | |
325 | // check for. !!! FIX !!! | |
326 | // Partially fixed: We check for I2SCNTRL in the JERRY I2S routine... | |
327 | // return GetWordFromButchSSI(offset, who); | |
328 | if (offset == LRXD || offset == RRXD) | |
329 | return 0x0000; | |
330 | else if (offset == LRXD + 2) | |
331 | return lrxd; | |
332 | else if (offset == RRXD + 2) | |
333 | return rrxd; | |
334 | ||
335 | return 0xFFFF; // May need SSTAT as well... (but may be a Jaguar II only feature) | |
336 | } | |
337 |