]> Shamusworld >> Repos - apple2/blob - src/sound.cpp
Added proper locking of conditional variable mutex
[apple2] / src / sound.cpp
1 //
2 // Sound Interface
3 //
4 // by James L. Hammons
5 // (C) 2005 Underground Software
6 //
7 // JLH = James L. Hammons <jlhamm@acm.org>
8 //
9 // WHO  WHEN        WHAT
10 // ---  ----------  ------------------------------------------------------------
11 // JLH  12/02/2005  Fixed a problem with sound callback thread signaling the
12 //                  main thread
13 // JLH  12/03/2005  Fixed sound callback dropping samples when the sample buffer
14 //                  is shorter than the callback sample buffer
15 //
16
17 // STILL TO DO:
18 //
19 // - Figure out why it's losing samples (Bard's Tale) [DONE]
20 // - Figure out why it's playing too fast
21 //
22
23 #include "sound.h"
24
25 #include <string.h>                                                             // For memset, memcpy
26 #include <SDL.h>
27 #include "log.h"
28
29 // Useful defines
30
31 //#define DEBUG
32
33 //#define SAMPLE_RATE                   (44100.0)
34 #define SAMPLE_RATE                     (48000.0)
35 #define SAMPLES_PER_FRAME       (SAMPLE_RATE / 60.0)
36 // ~ 21
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)
44
45 // Global variables
46
47
48 // Local variables
49
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;
61 static int8 sample;
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 };
65
66 // Private function prototypes
67
68 static void SDLSoundCallback(void * userdata, Uint8 * buffer, int length);
69
70 //
71 // Initialize the SDL sound system
72 //
73 void SoundInit(void)
74 {
75 #if 0
76 // To weed out problems for now...
77 return;
78 #endif
79
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)...
83         desired.channels = 1;
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;
88
89         if (SDL_OpenAudio(&desired, NULL) < 0)          // NULL means SDL guarantees what we want
90         {
91                 WriteLog("Sound: Failed to initialize SDL sound.\n");
92                 return;
93         }
94
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...
99         soundBufferPos = 0;
100         sampleBase = 0;
101         lastToggleCycles = 0;
102         samplePosition = 0;
103         sample = desired.silence;       // ? wilwok ? yes
104
105         SDL_PauseAudio(false);                                          // Start playback!
106         soundInitialized = true;
107         WriteLog("Sound: Successfully initialized.\n");
108 }
109
110 //
111 // Close down the SDL sound subsystem
112 //
113 void SoundDone(void)
114 {
115         if (soundInitialized)
116         {
117                 SDL_PauseAudio(true);
118                 SDL_CloseAudio();
119                 SDL_DestroyCond(conditional);
120                 SDL_DestroyMutex(mutex);
121                 SDL_DestroyMutex(mutex2);
122                 WriteLog("Sound: Done.\n");
123         }
124 }
125
126 //
127 // Sound card callback handler
128 //
129 static void SDLSoundCallback(void * userdata, Uint8 * buffer, int length)
130 {
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...)
137
138         // Let's try using a mutex for shared resource consumption...
139         SDL_mutexP(mutex2);
140
141         if (soundBufferPos < (uint32)length)            // The sound buffer is starved...
142         {
143 //printf("Sound buffer starved!\n");
144 //fflush(stdout);
145                 for(uint32 i=0; i<soundBufferPos; i++)
146                         buffer[i] = soundBuffer[i];
147
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!
156         }
157         else
158         {
159                 // Fill sound buffer with frame buffered sound
160                 memcpy(buffer, soundBuffer, length);
161                 soundBufferPos -= length;
162                 sampleBase -= length;
163
164                 // Move current buffer down to start
165                 for(uint32 i=0; i<soundBufferPos; i++)
166                         soundBuffer[i] = soundBuffer[length + i];
167         }
168
169         // Update our sample position
170         samplePosition += length;
171         // Free the mutex...
172         SDL_mutexV(mutex2);
173         // Wake up any threads waiting for the buffer to drain...
174         SDL_CondSignal(conditional);
175 }
176
177 // Need some interface functions here to take care of flipping the
178 // waveform at the correct time in the sound stream...
179
180 /*
181 Maybe set up a buffer 1 frame long (44100 / 60 = 735 bytes per frame)
182
183 Hmm. That's smaller than the sound buffer 2048 bytes... (About 2.75 frames needed to fill)
184
185 So... I guess what we could do is this:
186
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)
191 */
192
193 void ToggleSpeaker(uint64 elapsedCycles)
194 {
195         if (!soundInitialized)
196                 return;
197
198         uint64 deltaCycles = elapsedCycles - lastToggleCycles;
199
200 #if 0
201 if (time > 95085)//(time & 0x80000000)
202 {
203         WriteLog("ToggleSpeaker() given bad time value: %08X (%u)!\n", time, time);
204 //      fflush(stdout);
205 }
206 #endif
207
208         // 1.024 MHz / 60 = 17066.6... cycles (23.2199 cycles per sample)
209         // Need the last frame position in order to calculate correctly...
210         // (or do we?)
211
212 //      SDL_LockAudio();
213         SDL_mutexP(mutex2);
214 //      uint32 currentPos = sampleBase + (uint32)((double)elapsedCycles / CYCLES_PER_SAMPLE);
215         uint32 currentPos = (uint32)((double)deltaCycles / CYCLES_PER_SAMPLE);
216
217 /*
218 The problem:
219
220      ______ | ______________ | ______
221 ____|       |                |      |_______
222
223 Speaker is toggled, then not toggled for a while. How to find buffer position in the
224 last frame?
225
226 IRQ buffer len is 1024.
227
228 Could check current CPU clock, take delta. If delta > 1024, then ...
229
230 Could add # of cycles in IRQ to lastToggleCycles, then currentPos will be guaranteed
231 to fall within acceptable limits.
232
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,
235 for example
236
237 Another method would be to add to lastToggleCycles on every timeslice of the CPU,
238 just like we used to.
239
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.
243 */
244
245
246         if ((soundBufferPos + currentPos) > (SOUND_BUFFER_SIZE - 1))
247         {
248 #if 0
249 WriteLog("ToggleSpeaker() about to go into spinlock at time: %08X (%u) (sampleBase=%u)!\n", time, time, sampleBase);
250 #endif
251 // Still hanging on this spinlock...
252 // That could be because the "time" value is too high and so the buffer will NEVER be
253 // empty enough...
254 // Now that we're using a conditional, it seems to be working OK--though not perfectly...
255 /*
256 ToggleSpeaker() about to go into spinlock at time: 00004011 (16401) (sampleBase=3504)!
257 16401 -> 706 samples, 3504 + 706 = 4210
258
259 And it still thrashed the sound even though it didn't run into a spinlock...
260
261 Seems like it's OK now that I've fixed the buffer-less-than-length bug...
262 */
263 //              SDL_UnlockAudio();
264 //              SDL_CondWait(conditional, mutex);
265 //              SDL_LockAudio();
266 // Hm.
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_mutexP(mutex);                                                      // Must lock the mutex for the cond to work properly...
270 SDL_CondWait(conditional, mutex);//Sleep/wait for the sound thread
271         SDL_mutexV(mutex);                                                      // Must unlock the mutex for the cond to work properly...
272 SDL_mutexP(mutex2);//Re-lock it until we're done with it...
273
274 //              currentPos = sampleBase + (uint32)((double)deltaCycles / CYCLES_PER_SAMPLE);
275                 currentPos = (uint32)((double)deltaCycles / CYCLES_PER_SAMPLE);
276 #if 0
277 WriteLog("--> after spinlock (sampleBase=%u)...\n", sampleBase);
278 #endif
279         }
280
281         sample = (speakerState ? amplitude[ampPtr] : -amplitude[ampPtr]);
282
283         // currentPos is position from "zero" or soundBufferPos...
284         currentPos += soundBufferPos;
285
286         while (soundBufferPos < currentPos)
287                 soundBuffer[soundBufferPos++] = (uint8)sample;
288
289         // This is done *after* in case the buffer had a long dead spot (I think...)
290         speakerState = !speakerState;
291         sample = (speakerState ? amplitude[ampPtr] : -amplitude[ampPtr]);
292         lastToggleCycles = elapsedCycles;
293         SDL_mutexV(mutex2);
294 //      SDL_UnlockAudio();
295 }
296
297 void AddToSoundTimeBase(uint32 cycles)
298 {
299         if (!soundInitialized)
300                 return;
301
302 //      SDL_LockAudio();
303         SDL_mutexP(mutex2);
304         sampleBase += (uint32)((double)cycles / CYCLES_PER_SAMPLE);
305         SDL_mutexV(mutex2);
306 //      SDL_UnlockAudio();
307 }
308
309 void AdjustLastToggleCycles(uint64 elapsedCycles)
310 {
311 #if 0
312         if (!soundInitialized)
313                 return;
314
315         SDL_mutexP(mutex2);
316         lastToggleCycles += elapsedCycles;
317         SDL_mutexV(mutex2);
318
319 // We should also fill the buffer here as well, even if the speaker
320 // didn't toggle... !!! FIX !!!
321 #else
322 /*
323 BOOKKEEPING
324
325 We need to know the following:
326
327  o  Where in the sound buffer the base or "zero" time is
328  o  At what CPU timestamp the speaker was last toggled
329     NOTE: we keep things "right" by advancing this number every frame, even
330           if nothing happened! That way, we can keep track without having
331           to detect whether or not several frames have gone by without any
332           activity.
333
334 How to do it:
335
336 Every time the speaker is toggled, we move the base or "zero" time to the
337 current spot in the buffer. We also backfill the buffer up to that point with
338 the old toggle value. The next time the speaker is toggled, we measure the
339 difference in time between the last time it was toggled (the "zero") and now,
340 and repeat the cycle.
341
342 We handle dead spots by backfilling the buffer with the current toggle value
343 every frame--this way we don't have to worry about keeping current time and
344 crap like that. So, we have to move the "zero" the right amount, just like
345 in ToggleSpeaker(), and backfill only without toggling.
346 */
347 #warning "This is VERY similar to ToggleSpeaker(); merge into common function. !!! FIX !!!"
348         if (!soundInitialized)
349                 return;
350
351 #ifdef DEBUG
352 printf("SOUND: AdjustLastToggleCycles() start...\n");
353 #endif
354         // Step 1: Calculate delta time
355         uint64 deltaCycles = elapsedCycles - lastToggleCycles;
356
357         // Step 2: Calculate new buffer position
358         uint32 currentPos = (uint32)((double)deltaCycles / CYCLES_PER_SAMPLE);
359
360         // Step 3: Make sure there's room for it
361         // We need to lock since we touch both soundBuffer and soundBufferPos
362         SDL_mutexP(mutex2);
363         while ((soundBufferPos + currentPos) > (SOUND_BUFFER_SIZE - 1))
364         {
365                 // Hm.
366                 // This might not empty the buffer enough, causing hash and trash. !!! FIX !!! [DONE]
367                 SDL_mutexV(mutex2);//Release it so sound thread can get it,
368                 SDL_mutexP(mutex);                                                      // Must lock the mutex for the cond to work properly...
369                 SDL_CondWait(conditional, mutex);//Sleep/wait for the sound thread
370                 SDL_mutexV(mutex);                                                      // Must unlock the mutex for the cond to work properly...
371                 SDL_mutexP(mutex2);//Re-lock it until we're done with it...
372
373 //HMM, this doesn't need to lock or recalculate this value
374 //              currentPos = (uint32)((double)deltaCycles / CYCLES_PER_SAMPLE);
375         }
376
377         // Step 4: Backfill and adjust lastToggleCycles
378         // currentPos is position from "zero" or soundBufferPos...
379         currentPos += soundBufferPos;
380
381         // Backfill with current toggle state
382         while (soundBufferPos < currentPos)
383                 soundBuffer[soundBufferPos++] = (uint8)sample;
384
385         SDL_mutexV(mutex2);
386         lastToggleCycles = elapsedCycles;
387 #ifdef DEBUG
388 printf("SOUND: AdjustLastToggleCycles() end...\n");
389 #endif
390 #endif
391 }
392
393 void VolumeUp(void)
394 {
395         // Currently set for 8-bit samples
396         if (ampPtr < 8)
397                 ampPtr++;
398 }
399
400 void VolumeDown(void)
401 {
402         if (ampPtr > 0)
403                 ampPtr--;
404 }
405
406 uint8 GetVolume(void)
407 {
408         return ampPtr;
409 }
410
411 /*
412 HOW IT WORKS
413
414 the main thread adds the amount of cpu time elapsed to samplebase. togglespeaker uses
415 samplebase + current cpu time to find appropriate spot in buffer. it then fills the
416 buffer up to the current time with the old toggle value before flipping it. the sound
417 irq takes what it needs from the sound buffer and then adjusts both the buffer and
418 samplebase back the appropriate amount.
419
420
421 A better way might be as follows:
422
423 Keep timestamp array of speaker toggle times. In the sound routine, unpack as many as will
424 fit into the given buffer and keep going. Have the toggle function check to see if the
425 buffer is full, and if it is, way for a signal from the interrupt that there's room for
426 more. Can keep a circular buffer. Also, would need a timestamp buffer on the order of 2096
427 samples *in theory* could toggle each sample
428
429 Instead of a timestamp, just keep a delta. That way, don't need to deal with wrapping and
430 all that (though the timestamp could wrap--need to check into that)
431
432 Need to consider corner cases where a sound IRQ happens but no speaker toggle happened.
433
434 If (delta > SAMPLES_PER_FRAME) then
435
436 Here's the relevant cases:
437
438 delta < SAMPLES_PER_FRAME -> Change happened within this time frame, so change buffer
439 frame came and went, no change -> fill buffer with last value
440 How to detect: Have bool bufferWasTouched = true when ToggleSpeaker() is called.
441 Clear bufferWasTouched each frame.
442
443 Two major cases here:
444
445  o  Buffer is touched on current frame
446  o  Buffer is untouched on current frame
447
448 In the first case, it doesn't matter too much if the previous frame was touched or not,
449 we don't really care except in finding the correct spot in the buffer to put our change
450 in. In the second case, we need to tell the IRQ that nothing happened and to continue
451 to output the same value.
452
453 SO: How to synchronize the regular frame buffer with the IRQ buffer?
454
455 What happens:
456   Sound IRQ --> Every 1024 sample period (@ 44.1 KHz = 0.0232s)
457   Emulation --> Render a frame --> 1/60 sec --> 735 samples
458     --> sound buffer is filled
459
460 Since the emulation is faster than the SIRQ the sound buffer should fill up
461 prior to dumping it to the sound card.
462
463 Problem is this: If silence happens for a long time then ToggleSpeaker is never
464 called and the sound buffer has stale data; at least until soundBufferPos goes to
465 zero and stays there...
466
467 BUT this should be handled correctly by toggling the speaker value *after* filling
468 the sound buffer...
469
470 Still getting random clicks when running...
471 (This may be due to the lock/unlock sound happening in ToggleSpeaker()...)
472 */
473
474
475
476
477
478
479