]> Shamusworld >> Repos - apple2/blob - src/sound.cpp
Initial attempt at multithreaded implementation: CPU is now in its own thread.
[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
30 //#define SAMPLE_RATE                   (44100.0)
31 #define SAMPLE_RATE                     (48000.0)
32 #define SAMPLES_PER_FRAME       (SAMPLE_RATE / 60.0)
33 #define CYCLES_PER_SAMPLE       (1024000.0 / SAMPLE_RATE)
34 #define SOUND_BUFFER_SIZE       (8192)
35 //#define AMPLITUDE                     (16)                            // -32 - +32 seems to be plenty loud!
36
37 // Global variables
38
39
40 // Local variables
41
42 static SDL_AudioSpec desired;
43 static bool soundInitialized = false;
44 static bool speakerState = false;
45 static uint8 soundBuffer[SOUND_BUFFER_SIZE];
46 static uint32 soundBufferPos;
47 static uint32 sampleBase;
48 static uint64 lastToggleCycles;
49 static uint64 samplePosition;
50 static SDL_cond * conditional = NULL;
51 static SDL_mutex * mutex = NULL;
52 static SDL_mutex * mutex2 = NULL;
53 static uint8 ampPtr = 5;
54 static uint16 amplitude[17] = { 0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048,
55         4096, 8192, 16384, 32768 };
56
57 // Private function prototypes
58
59 static void SDLSoundCallback(void * userdata, Uint8 * buffer, int length);
60
61 //
62 // Initialize the SDL sound system
63 //
64 void SoundInit(void)
65 {
66 #if 1
67 // To weed out problems for now...
68 return;
69 #endif
70
71         desired.freq = SAMPLE_RATE;                                     // SDL will do conversion on the fly, if it can't get the exact rate. Nice!
72         desired.format = AUDIO_S8;                                      // This uses the native endian (for portability)...
73 //      desired.format = AUDIO_S16SYS;                          // This uses the native endian (for portability)...
74         desired.channels = 1;
75 //      desired.samples = 4096;                                         // Let's try a 4K buffer (can always go lower)
76 //      desired.samples = 2048;                                         // Let's try a 2K buffer (can always go lower)
77         desired.samples = 1024;                                         // Let's try a 1K buffer (can always go lower)
78         desired.callback = SDLSoundCallback;
79
80         if (SDL_OpenAudio(&desired, NULL) < 0)          // NULL means SDL guarantees what we want
81         {
82                 WriteLog("Sound: Failed to initialize SDL sound.\n");
83                 return;
84         }
85
86         conditional = SDL_CreateCond();
87         mutex = SDL_CreateMutex();
88         mutex2 = SDL_CreateMutex();// Let's try real signalling...
89         SDL_mutexP(mutex);                                                      // Must lock the mutex for the cond to work properly...
90         soundBufferPos = 0;
91         sampleBase = 0;
92         lastToggleCycles = 0;
93         samplePosition = 0;
94
95         SDL_PauseAudio(false);                                          // Start playback!
96         soundInitialized = true;
97         WriteLog("Sound: Successfully initialized.\n");
98 }
99
100 //
101 // Close down the SDL sound subsystem
102 //
103 void SoundDone(void)
104 {
105         if (soundInitialized)
106         {
107                 SDL_PauseAudio(true);
108                 SDL_CloseAudio();
109                 SDL_DestroyCond(conditional);
110                 SDL_DestroyMutex(mutex);
111                 SDL_DestroyMutex(mutex2);
112                 WriteLog("Sound: Done.\n");
113         }
114 }
115
116 //
117 // Sound card callback handler
118 //
119 static void SDLSoundCallback(void * userdata, Uint8 * buffer, int length)
120 {
121         // The sound buffer should only starve when starting which will cause it to
122         // lag behind the emulation at most by around 1 frame...
123         // (Actually, this should never happen since we fill the buffer beforehand.)
124         // (But, then again, if the sound hasn't been toggled for a while, then this
125         //  makes perfect sense as the buffer won't have been filled at all!)
126
127         // Let's try using a mutex for shared resource consumption...
128         SDL_mutexP(mutex2);
129
130         if (soundBufferPos < (uint32)length)            // The sound buffer is starved...
131         {
132 //printf("Sound buffer starved!\n");
133 //fflush(stdout);
134                 for(uint32 i=0; i<soundBufferPos; i++)
135                         buffer[i] = soundBuffer[i];
136
137                 // Fill buffer with last value
138                 memset(buffer + soundBufferPos, (uint8)(speakerState ? amplitude[ampPtr] : -amplitude[ampPtr]), length - soundBufferPos);
139                 soundBufferPos = 0;                                             // Reset soundBufferPos to start of buffer...
140                 sampleBase = 0;                                                 // & sampleBase...
141 //Ick. This should never happen!
142 //Actually, this probably happens a lot. (?)
143 //              SDL_CondSignal(conditional);                    // Wake up any threads waiting for the buffer to drain...
144 //              return;                                                                 // & bail!
145         }
146         else
147         {
148                 // Fill sound buffer with frame buffered sound
149                 memcpy(buffer, soundBuffer, length);
150                 soundBufferPos -= length;
151                 sampleBase -= length;
152
153                 // Move current buffer down to start
154                 for(uint32 i=0; i<soundBufferPos; i++)
155                         soundBuffer[i] = soundBuffer[length + i];
156         }
157
158         // Update our sample position
159         samplePosition += length;
160         // Free the mutex...
161         SDL_mutexV(mutex2);
162         // Wake up any threads waiting for the buffer to drain...
163         SDL_CondSignal(conditional);
164 }
165
166 // Need some interface functions here to take care of flipping the
167 // waveform at the correct time in the sound stream...
168
169 /*
170 Maybe set up a buffer 1 frame long (44100 / 60 = 735 bytes per frame)
171
172 Hmm. That's smaller than the sound buffer 2048 bytes... (About 2.75 frames needed to fill)
173
174 So... I guess what we could do is this:
175
176 -- Execute V65C02 for one frame. The read/writes at I/O address $C030 fill up the buffer
177    to the current time position.
178 -- The sound callback function copies the pertinent area out of the buffer, resets
179    the time position back (or copies data down from what it took out)
180 */
181
182 void ToggleSpeaker(uint64 elapsedCycles)
183 {
184         if (!soundInitialized)
185                 return;
186
187         uint64 deltaCycles = elapsedCycles - lastToggleCycles;
188
189 #if 0
190 if (time > 95085)//(time & 0x80000000)
191 {
192         WriteLog("ToggleSpeaker() given bad time value: %08X (%u)!\n", time, time);
193 //      fflush(stdout);
194 }
195 #endif
196
197         // 1.024 MHz / 60 = 17066.6... cycles (23.2199 cycles per sample)
198         // Need the last frame position in order to calculate correctly...
199         // (or do we?)
200
201 //      SDL_LockAudio();
202         SDL_mutexP(mutex2);
203 //      uint32 currentPos = sampleBase + (uint32)((double)elapsedCycles / CYCLES_PER_SAMPLE);
204         uint32 currentPos = (uint32)((double)deltaCycles / CYCLES_PER_SAMPLE);
205
206 /*
207 The problem:
208
209      ______ | ______________ | ______
210 ____|       |                |      |_______
211
212 Speaker is toggled, then not toggled for a while. How to find buffer position in the
213 last frame?
214
215 IRQ buffer len is 1024.
216
217 Could check current CPU clock, take delta. If delta > 1024, then ...
218
219 Could add # of cycles in IRQ to lastToggleCycles, then currentPos will be guaranteed
220 to fall within acceptable limits.
221 */
222
223
224         if (currentPos > SOUND_BUFFER_SIZE - 1)
225         {
226 #if 0
227 WriteLog("ToggleSpeaker() about to go into spinlock at time: %08X (%u) (sampleBase=%u)!\n", time, time, sampleBase);
228 #endif
229 // Still hanging on this spinlock...
230 // That could be because the "time" value is too high and so the buffer will NEVER be
231 // empty enough...
232 // Now that we're using a conditional, it seems to be working OK--though not perfectly...
233 /*
234 ToggleSpeaker() about to go into spinlock at time: 00004011 (16401) (sampleBase=3504)!
235 16401 -> 706 samples, 3504 + 706 = 4210
236
237 And it still thrashed the sound even though it didn't run into a spinlock...
238
239 Seems like it's OK now that I've fixed the buffer-less-than-length bug...
240 */
241 //              SDL_UnlockAudio();
242 //              SDL_CondWait(conditional, mutex);
243 //              SDL_LockAudio();
244 // Hm.
245 SDL_mutexV(mutex2);//Release it so sound thread can get it,
246 SDL_CondWait(conditional, mutex);//Sleep/wait for the sound thread
247 SDL_mutexP(mutex2);//Re-lock it until we're done with it...
248
249                 currentPos = sampleBase + (uint32)((double)elapsedCycles / CYCLES_PER_SAMPLE);
250 #if 0
251 WriteLog("--> after spinlock (sampleBase=%u)...\n", sampleBase);
252 #endif
253         }
254
255         int8 sample = (speakerState ? amplitude[ampPtr] : -amplitude[ampPtr]);
256
257         while (soundBufferPos < currentPos)
258                 soundBuffer[soundBufferPos++] = (uint8)sample;
259
260         // This is done *after* in case the buffer had a long dead spot (I think...)
261         speakerState = !speakerState;
262         SDL_mutexV(mutex2);
263 //      SDL_UnlockAudio();
264 }
265
266 void AddToSoundTimeBase(uint32 cycles)
267 {
268         if (!soundInitialized)
269                 return;
270
271 //      SDL_LockAudio();
272         SDL_mutexP(mutex2);
273         sampleBase += (uint32)((double)cycles / CYCLES_PER_SAMPLE);
274         SDL_mutexV(mutex2);
275 //      SDL_UnlockAudio();
276 }
277
278 void VolumeUp(void)
279 {
280         // Currently set for 8-bit samples
281         if (ampPtr < 8)
282                 ampPtr++;
283 }
284
285 void VolumeDown(void)
286 {
287         if (ampPtr > 0)
288                 ampPtr--;
289 }
290
291 uint8 GetVolume(void)
292 {
293         return ampPtr;
294 }
295
296 /*
297 HOW IT WORKS
298
299 the main thread adds the amount of cpu time elapsed to samplebase. togglespeaker uses
300 samplebase + current cpu time to find appropriate spot in buffer. it then fills the
301 buffer up to the current time with the old toggle value before flipping it. the sound
302 irq takes what it needs from the sound buffer and then adjusts both the buffer and
303 samplebase back the appropriate amount.
304
305
306 A better way might be as follows:
307
308 Keep timestamp array of speaker toggle times. In the sound routine, unpack as many as will
309 fit into the given buffer and keep going. Have the toggle function check to see if the
310 buffer is full, and if it is, way for a signal from the interrupt that there's room for
311 more. Can keep a circular buffer. Also, would need a timestamp buffer on the order of 2096
312 samples *in theory* could toggle each sample
313
314 Instead of a timestamp, just keep a delta. That way, don't need to deal with wrapping and
315 all that (though the timestamp could wrap--need to check into that)
316
317 Need to consider corner cases where a sound IRQ happens but no speaker toggle happened.
318
319 If (delta > SAMPLES_PER_FRAME) then
320
321 Here's the relevant cases:
322
323 delta < SAMPLES_PER_FRAME -> Change happened within this time frame, so change buffer
324 frame came and went, no change -> fill buffer with last value
325 How to detect: Have bool bufferWasTouched = true when ToggleSpeaker() is called.
326 Clear bufferWasTouched each frame.
327
328 Two major cases here:
329
330  o  Buffer is touched on current frame
331  o  Buffer is untouched on current frame
332
333 In the first case, it doesn't matter too much if the previous frame was touched or not,
334 we don't really care except in finding the correct spot in the buffer to put our change
335 in. In the second case, we need to tell the IRQ that nothing happened and to continue
336 to output the same value.
337
338 SO: How to synchronize the regular frame buffer with the IRQ buffer?
339
340 What happens:
341   Sound IRQ --> Every 1024 sample period (@ 44.1 KHz = 0.0232s)
342   Emulation --> Render a frame --> 1/60 sec --> 735 samples
343     --> sound buffer is filled
344
345 Since the emulation is faster than the SIRQ the sound buffer should fill up
346 prior to dumping it to the sound card.
347
348 Problem is this: If silence happens for a long time then ToggleSpeaker is never
349 called and the sound buffer has stale data; at least until soundBufferPos goes to
350 zero and stays there...
351
352 BUT this should be handled correctly by toggling the speaker value *after* filling
353 the sound buffer...
354
355 Still getting random clicks when running...
356 (This may be due to the lock/unlock sound happening in ToggleSpeaker()...)
357 */
358
359
360
361
362
363
364