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