5 // (C) 2005 Underground Software
7 // JLH = James L. Hammons <jlhamm@acm.org>
10 // --- ---------- ------------------------------------------------------------
11 // JLH 12/02/2005 Fixed a problem with sound callback thread signaling the
13 // JLH 12/03/2005 Fixed sound callback dropping samples when the sample buffer
14 // is shorter than the callback sample buffer
19 // - Figure out why it's losing samples (Bard's Tale) [DONE]
20 // - Figure out why it's playing too fast
25 #include <string.h> // For memset, memcpy
33 //#define SAMPLE_RATE (44100.0)
34 #define SAMPLE_RATE (48000.0)
35 #define SAMPLES_PER_FRAME (SAMPLE_RATE / 60.0)
37 //#define CYCLES_PER_SAMPLE (1024000.0 / SAMPLE_RATE)
38 // ~ 17 (lower pitched than above...!)
39 // Makes sense, as this is the divisor for # of cycles passed
40 #define CYCLES_PER_SAMPLE (800000.0 / SAMPLE_RATE)
41 //nope, too high #define CYCLES_PER_SAMPLE (960000.0 / SAMPLE_RATE)
42 //#define SOUND_BUFFER_SIZE (8192)
43 #define SOUND_BUFFER_SIZE (16384)
50 static SDL_AudioSpec desired;
51 static bool soundInitialized = false;
52 static bool speakerState = false;
53 static uint8 soundBuffer[SOUND_BUFFER_SIZE];
54 static uint32 soundBufferPos;
55 static uint32 sampleBase;
56 static uint64 lastToggleCycles;
57 static uint64 samplePosition;
58 static SDL_cond * conditional = NULL;
59 static SDL_mutex * mutex = NULL;
60 static SDL_mutex * mutex2 = NULL;
62 static uint8 ampPtr = 5; // Start with -16 - +16
63 static uint16 amplitude[17] = { 0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048,
64 4096, 8192, 16384, 32768 };
66 // Private function prototypes
68 static void SDLSoundCallback(void * userdata, Uint8 * buffer, int length);
71 // Initialize the SDL sound system
76 // To weed out problems for now...
80 desired.freq = SAMPLE_RATE; // SDL will do conversion on the fly, if it can't get the exact rate. Nice!
81 desired.format = AUDIO_S8; // This uses the native endian (for portability)...
82 // desired.format = AUDIO_S16SYS; // This uses the native endian (for portability)...
84 // desired.samples = 4096; // Let's try a 4K buffer (can always go lower)
85 // desired.samples = 2048; // Let's try a 2K buffer (can always go lower)
86 desired.samples = 1024; // Let's try a 1K buffer (can always go lower)
87 desired.callback = SDLSoundCallback;
89 if (SDL_OpenAudio(&desired, NULL) < 0) // NULL means SDL guarantees what we want
91 WriteLog("Sound: Failed to initialize SDL sound.\n");
95 conditional = SDL_CreateCond();
96 mutex = SDL_CreateMutex();
97 mutex2 = SDL_CreateMutex();// Let's try real signalling...
98 SDL_mutexP(mutex); // Must lock the mutex for the cond to work properly...
101 lastToggleCycles = 0;
103 sample = desired.silence; // ? wilwok ? yes
105 SDL_PauseAudio(false); // Start playback!
106 soundInitialized = true;
107 WriteLog("Sound: Successfully initialized.\n");
111 // Close down the SDL sound subsystem
115 if (soundInitialized)
117 SDL_PauseAudio(true);
119 SDL_DestroyCond(conditional);
120 SDL_DestroyMutex(mutex);
121 SDL_DestroyMutex(mutex2);
122 WriteLog("Sound: Done.\n");
127 // Sound card callback handler
129 static void SDLSoundCallback(void * userdata, Uint8 * buffer, int length)
131 // The sound buffer should only starve when starting which will cause it to
132 // lag behind the emulation at most by around 1 frame...
133 // (Actually, this should never happen since we fill the buffer beforehand.)
134 // (But, then again, if the sound hasn't been toggled for a while, then this
135 // makes perfect sense as the buffer won't have been filled at all!)
136 // (Should NOT starve now, now that we properly handle frame edges...)
138 // Let's try using a mutex for shared resource consumption...
141 if (soundBufferPos < (uint32)length) // The sound buffer is starved...
143 //printf("Sound buffer starved!\n");
145 for(uint32 i=0; i<soundBufferPos; i++)
146 buffer[i] = soundBuffer[i];
148 // Fill buffer with last value
149 // memset(buffer + soundBufferPos, (uint8)(speakerState ? amplitude[ampPtr] : -amplitude[ampPtr]), length - soundBufferPos);
150 memset(buffer + soundBufferPos, (uint8)sample, length - soundBufferPos); soundBufferPos = 0; // Reset soundBufferPos to start of buffer...
151 sampleBase = 0; // & sampleBase...
152 //Ick. This should never happen!
153 //Actually, this probably happens a lot. (?)
154 // SDL_CondSignal(conditional); // Wake up any threads waiting for the buffer to drain...
155 // return; // & bail!
159 // Fill sound buffer with frame buffered sound
160 memcpy(buffer, soundBuffer, length);
161 soundBufferPos -= length;
162 sampleBase -= length;
164 // Move current buffer down to start
165 for(uint32 i=0; i<soundBufferPos; i++)
166 soundBuffer[i] = soundBuffer[length + i];
169 // Update our sample position
170 samplePosition += length;
173 // Wake up any threads waiting for the buffer to drain...
174 SDL_CondSignal(conditional);
177 // Need some interface functions here to take care of flipping the
178 // waveform at the correct time in the sound stream...
181 Maybe set up a buffer 1 frame long (44100 / 60 = 735 bytes per frame)
183 Hmm. That's smaller than the sound buffer 2048 bytes... (About 2.75 frames needed to fill)
185 So... I guess what we could do is this:
187 -- Execute V65C02 for one frame. The read/writes at I/O address $C030 fill up the buffer
188 to the current time position.
189 -- The sound callback function copies the pertinent area out of the buffer, resets
190 the time position back (or copies data down from what it took out)
193 void ToggleSpeaker(uint64 elapsedCycles)
195 if (!soundInitialized)
198 uint64 deltaCycles = elapsedCycles - lastToggleCycles;
201 if (time > 95085)//(time & 0x80000000)
203 WriteLog("ToggleSpeaker() given bad time value: %08X (%u)!\n", time, time);
208 // 1.024 MHz / 60 = 17066.6... cycles (23.2199 cycles per sample)
209 // Need the last frame position in order to calculate correctly...
214 // uint32 currentPos = sampleBase + (uint32)((double)elapsedCycles / CYCLES_PER_SAMPLE);
215 uint32 currentPos = (uint32)((double)deltaCycles / CYCLES_PER_SAMPLE);
220 ______ | ______________ | ______
223 Speaker is toggled, then not toggled for a while. How to find buffer position in the
226 IRQ buffer len is 1024.
228 Could check current CPU clock, take delta. If delta > 1024, then ...
230 Could add # of cycles in IRQ to lastToggleCycles, then currentPos will be guaranteed
231 to fall within acceptable limits.
233 This *should* work, but if the IRQ isn't scheduled & etc, could screw timing up.
234 Need to have a way to suspend IRQ thread as well as CPU thread when in the GUI,
237 Another method would be to add to lastToggleCycles on every timeslice of the CPU,
238 just like we used to.
240 Or, run the CPU for CYCLES_PER_SAMPLE and take a sample, then copy the buffer over
241 at the end of the timeslice. That way, we could just fill the buffer and let the
242 IRQ handle draining it. No muss, no fuss.
246 if ((soundBufferPos + currentPos) > (SOUND_BUFFER_SIZE - 1))
249 WriteLog("ToggleSpeaker() about to go into spinlock at time: %08X (%u) (sampleBase=%u)!\n", time, time, sampleBase);
251 // Still hanging on this spinlock...
252 // That could be because the "time" value is too high and so the buffer will NEVER be
254 // Now that we're using a conditional, it seems to be working OK--though not perfectly...
256 ToggleSpeaker() about to go into spinlock at time: 00004011 (16401) (sampleBase=3504)!
257 16401 -> 706 samples, 3504 + 706 = 4210
259 And it still thrashed the sound even though it didn't run into a spinlock...
261 Seems like it's OK now that I've fixed the buffer-less-than-length bug...
263 // SDL_UnlockAudio();
264 // SDL_CondWait(conditional, mutex);
267 // This might not empty the buffer enough, causing hash and trash. !!! FIX !!!
268 SDL_mutexV(mutex2);//Release it so sound thread can get it,
269 SDL_CondWait(conditional, mutex);//Sleep/wait for the sound thread
270 SDL_mutexP(mutex2);//Re-lock it until we're done with it...
272 // currentPos = sampleBase + (uint32)((double)deltaCycles / CYCLES_PER_SAMPLE);
273 currentPos = (uint32)((double)deltaCycles / CYCLES_PER_SAMPLE);
275 WriteLog("--> after spinlock (sampleBase=%u)...\n", sampleBase);
279 sample = (speakerState ? amplitude[ampPtr] : -amplitude[ampPtr]);
281 // currentPos is position from "zero" or soundBufferPos...
282 currentPos += soundBufferPos;
284 while (soundBufferPos < currentPos)
285 soundBuffer[soundBufferPos++] = (uint8)sample;
287 // This is done *after* in case the buffer had a long dead spot (I think...)
288 speakerState = !speakerState;
289 sample = (speakerState ? amplitude[ampPtr] : -amplitude[ampPtr]);
290 lastToggleCycles = elapsedCycles;
292 // SDL_UnlockAudio();
295 void AddToSoundTimeBase(uint32 cycles)
297 if (!soundInitialized)
302 sampleBase += (uint32)((double)cycles / CYCLES_PER_SAMPLE);
304 // SDL_UnlockAudio();
307 void AdjustLastToggleCycles(uint64 elapsedCycles)
310 if (!soundInitialized)
314 lastToggleCycles += elapsedCycles;
317 // We should also fill the buffer here as well, even if the speaker
318 // didn't toggle... !!! FIX !!!
323 We need to know the following:
325 o Where in the sound buffer the base or "zero" time is
326 o At what CPU timestamp the speaker was last toggled
327 NOTE: we keep things "right" by advancing this number every frame, even
328 if nothing happened! That way, we can keep track without having
329 to detect whether or not several frames have gone by without any
334 Every time the speaker is toggled, we move the base or "zero" time to the
335 current spot in the buffer. We also backfill the buffer up to that point with
336 the old toggle value. The next time the speaker is toggled, we measure the
337 difference in time between the last time it was toggled (the "zero") and now,
338 and repeat the cycle.
340 We handle dead spots by backfilling the buffer with the current toggle value
341 every frame--this way we don't have to worry about keeping current time and
342 crap like that. So, we have to move the "zero" the right amount, just like
343 in ToggleSpeaker(), and backfill only without toggling.
345 #warning "This is VERY similar to ToggleSpeaker(); merge into common function. !!! FIX !!!"
346 if (!soundInitialized)
350 printf("SOUND: AdjustLastToggleCycles() start...\n");
352 // Step 1: Calculate delta time
353 uint64 deltaCycles = elapsedCycles - lastToggleCycles;
355 // Step 2: Calculate new buffer position
356 uint32 currentPos = (uint32)((double)deltaCycles / CYCLES_PER_SAMPLE);
358 // Step 3: Make sure there's room for it
359 // We need to lock since we touch both soundBuffer and soundBufferPos
361 while ((soundBufferPos + currentPos) > (SOUND_BUFFER_SIZE - 1))
364 // This might not empty the buffer enough, causing hash and trash. !!! FIX !!! [DONE]
365 SDL_mutexV(mutex2);//Release it so sound thread can get it,
366 SDL_CondWait(conditional, mutex);//Sleep/wait for the sound thread
367 SDL_mutexP(mutex2);//Re-lock it until we're done with it...
369 //HMM, this doesn't need to lock or recalculate this value
370 // currentPos = (uint32)((double)deltaCycles / CYCLES_PER_SAMPLE);
373 // Step 4: Backfill and adjust lastToggleCycles
374 // currentPos is position from "zero" or soundBufferPos...
375 currentPos += soundBufferPos;
377 // Backfill with current toggle state
378 while (soundBufferPos < currentPos)
379 soundBuffer[soundBufferPos++] = (uint8)sample;
382 lastToggleCycles = elapsedCycles;
384 printf("SOUND: AdjustLastToggleCycles() end...\n");
391 // Currently set for 8-bit samples
396 void VolumeDown(void)
402 uint8 GetVolume(void)
410 the main thread adds the amount of cpu time elapsed to samplebase. togglespeaker uses
411 samplebase + current cpu time to find appropriate spot in buffer. it then fills the
412 buffer up to the current time with the old toggle value before flipping it. the sound
413 irq takes what it needs from the sound buffer and then adjusts both the buffer and
414 samplebase back the appropriate amount.
417 A better way might be as follows:
419 Keep timestamp array of speaker toggle times. In the sound routine, unpack as many as will
420 fit into the given buffer and keep going. Have the toggle function check to see if the
421 buffer is full, and if it is, way for a signal from the interrupt that there's room for
422 more. Can keep a circular buffer. Also, would need a timestamp buffer on the order of 2096
423 samples *in theory* could toggle each sample
425 Instead of a timestamp, just keep a delta. That way, don't need to deal with wrapping and
426 all that (though the timestamp could wrap--need to check into that)
428 Need to consider corner cases where a sound IRQ happens but no speaker toggle happened.
430 If (delta > SAMPLES_PER_FRAME) then
432 Here's the relevant cases:
434 delta < SAMPLES_PER_FRAME -> Change happened within this time frame, so change buffer
435 frame came and went, no change -> fill buffer with last value
436 How to detect: Have bool bufferWasTouched = true when ToggleSpeaker() is called.
437 Clear bufferWasTouched each frame.
439 Two major cases here:
441 o Buffer is touched on current frame
442 o Buffer is untouched on current frame
444 In the first case, it doesn't matter too much if the previous frame was touched or not,
445 we don't really care except in finding the correct spot in the buffer to put our change
446 in. In the second case, we need to tell the IRQ that nothing happened and to continue
447 to output the same value.
449 SO: How to synchronize the regular frame buffer with the IRQ buffer?
452 Sound IRQ --> Every 1024 sample period (@ 44.1 KHz = 0.0232s)
453 Emulation --> Render a frame --> 1/60 sec --> 735 samples
454 --> sound buffer is filled
456 Since the emulation is faster than the SIRQ the sound buffer should fill up
457 prior to dumping it to the sound card.
459 Problem is this: If silence happens for a long time then ToggleSpeaker is never
460 called and the sound buffer has stale data; at least until soundBufferPos goes to
461 zero and stays there...
463 BUT this should be handled correctly by toggling the speaker value *after* filling
466 Still getting random clicks when running...
467 (This may be due to the lock/unlock sound happening in ToggleSpeaker()...)