]> Shamusworld >> Repos - apple2/blob - src/sound.cpp
Added miscellaneous warnings, minor documentation
[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 //#define WRITE_OUT_WAVE
33
34 // This is odd--seems to be working properly now! Maybe a bug in the SDL sound code?
35 // Actually, it still doesn't sound right... Sounds too slow now. :-/
36 // But then again, it's difficult to tell. Sometimes it slows waaaaaay down, but generally
37 // seems to be OK other than that
38 // Also, it could be that the discrepancy in pitch is due to the V65C02 and it's lack of
39 // cycle accuracy...
40
41 //#define SAMPLE_RATE                   (44100.0)
42 #define SAMPLE_RATE                     (48000.0)
43 #define SAMPLES_PER_FRAME       (SAMPLE_RATE / 60.0)
44 // This works for AppleWin but not here... ??? WHY ???
45 // ~ 21
46 #define CYCLES_PER_SAMPLE       (1024000.0 / SAMPLE_RATE)
47 // ~ 17 (lower pitched than above...!)
48 // Makes sense, as this is the divisor for # of cycles passed
49 //#define CYCLES_PER_SAMPLE     (800000.0 / SAMPLE_RATE)
50 // This seems about right, compared to AppleWin--but AW runs @ 1.024 MHz
51 // 23 (1.024) vs. 20 (0.900)
52 //#define CYCLES_PER_SAMPLE     (900000.0 / SAMPLE_RATE)
53 //nope, too high #define CYCLES_PER_SAMPLE      (960000.0 / SAMPLE_RATE)
54 //#define CYCLES_PER_SAMPLE 21
55 //#define SOUND_BUFFER_SIZE     (8192)
56 #define SOUND_BUFFER_SIZE       (32768)
57
58 // Global variables
59
60
61 // Local variables
62
63 static SDL_AudioSpec desired, obtained;
64 static bool soundInitialized = false;
65 static bool speakerState = false;
66 static int16 soundBuffer[SOUND_BUFFER_SIZE];
67 static uint32 soundBufferPos;
68 static uint64 lastToggleCycles;
69 static SDL_cond * conditional = NULL;
70 static SDL_mutex * mutex = NULL;
71 static SDL_mutex * mutex2 = NULL;
72 static int16 sample;
73 static uint8 ampPtr = 14;                                               // Start with -16 - +16
74 static int16 amplitude[17] = { 0, 1, 2, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047,
75         4095, 8191, 16383, 32767 };
76 #ifdef WRITE_OUT_WAVE
77 static FILE * fp = NULL;
78 #endif
79
80 // Private function prototypes
81
82 static void SDLSoundCallback(void * userdata, Uint8 * buffer, int length);
83
84 //
85 // Initialize the SDL sound system
86 //
87 void SoundInit(void)
88 {
89 #if 0
90 // To weed out problems for now...
91 return;
92 #endif
93
94         desired.freq = SAMPLE_RATE;                                     // SDL will do conversion on the fly, if it can't get the exact rate. Nice!
95 //      desired.format = AUDIO_S8;                                      // This uses the native endian (for portability)...
96         desired.format = AUDIO_S16SYS;                          // This uses the native endian (for portability)...
97         desired.channels = 1;
98 //      desired.samples = 4096;                                         // Let's try a 4K buffer (can always go lower)
99 //      desired.samples = 2048;                                         // Let's try a 2K buffer (can always go lower)
100         desired.samples = 1024;                                         // Let's try a 1K buffer (can always go lower)
101         desired.callback = SDLSoundCallback;
102
103 //      if (SDL_OpenAudio(&desired, NULL) < 0)          // NULL means SDL guarantees what we want
104 //When doing it this way, we need to check to see if we got what we asked for...
105         if (SDL_OpenAudio(&desired, &obtained) < 0)
106         {
107                 WriteLog("Sound: Failed to initialize SDL sound.\n");
108                 return;
109         }
110
111         conditional = SDL_CreateCond();
112         mutex = SDL_CreateMutex();
113         mutex2 = SDL_CreateMutex();// Let's try real signalling...
114         soundBufferPos = 0;
115         lastToggleCycles = 0;
116         sample = desired.silence;       // ? wilwok ? yes
117
118         SDL_PauseAudio(false);                                          // Start playback!
119         soundInitialized = true;
120         WriteLog("Sound: Successfully initialized.\n");
121
122 #ifdef WRITE_OUT_WAVE
123         fp = fopen("./apple2.wav", "wb");
124 #endif
125 }
126
127 //
128 // Close down the SDL sound subsystem
129 //
130 void SoundDone(void)
131 {
132         if (soundInitialized)
133         {
134                 SDL_PauseAudio(true);
135                 SDL_CloseAudio();
136                 SDL_DestroyCond(conditional);
137                 SDL_DestroyMutex(mutex);
138                 SDL_DestroyMutex(mutex2);
139                 WriteLog("Sound: Done.\n");
140
141 #ifdef WRITE_OUT_WAVE
142                 fclose(fp);
143 #endif
144         }
145 }
146
147 //
148 // Sound card callback handler
149 //
150 static void SDLSoundCallback(void * userdata, Uint8 * buffer8, int length8)
151 {
152         // The sound buffer should only starve when starting which will cause it to
153         // lag behind the emulation at most by around 1 frame...
154         // (Actually, this should never happen since we fill the buffer beforehand.)
155         // (But, then again, if the sound hasn't been toggled for a while, then this
156         //  makes perfect sense as the buffer won't have been filled at all!)
157         // (Should NOT starve now, now that we properly handle frame edges...)
158
159         // Let's try using a mutex for shared resource consumption...
160 //Actually, I think Lock/UnlockAudio() does this already...
161         SDL_mutexP(mutex2);
162
163         // Recast this as a 16-bit type...
164         int16 * buffer = (int16 *)buffer8;
165         uint32 length = (uint32)length8 / 2;
166
167         if (soundBufferPos < length)                            // The sound buffer is starved...
168         {
169                 for(uint32 i=0; i<soundBufferPos; i++)
170                         buffer[i] = soundBuffer[i];
171
172                 // Fill buffer with last value
173 //              memset(buffer + soundBufferPos, (uint8)sample, length - soundBufferPos);
174                 for(uint32 i=soundBufferPos; i<length; i++)
175                         buffer[i] = (uint16)sample;
176                 soundBufferPos = 0;                                             // Reset soundBufferPos to start of buffer...
177         }
178         else
179         {
180                 // Fill sound buffer with frame buffered sound
181 //              memcpy(buffer, soundBuffer, length);
182                 for(uint32 i=0; i<length; i++)
183                         buffer[i] = soundBuffer[i];
184                 soundBufferPos -= length;
185
186                 // Move current buffer down to start
187                 for(uint32 i=0; i<soundBufferPos; i++)
188                         soundBuffer[i] = soundBuffer[length + i];
189         }
190
191         // Free the mutex...
192         SDL_mutexV(mutex2);
193         // Wake up any threads waiting for the buffer to drain...
194         SDL_CondSignal(conditional);
195 }
196
197 // Need some interface functions here to take care of flipping the
198 // waveform at the correct time in the sound stream...
199
200 /*
201 Maybe set up a buffer 1 frame long (44100 / 60 = 735 bytes per frame)
202
203 Hmm. That's smaller than the sound buffer 2048 bytes... (About 2.75 frames needed to fill)
204
205 So... I guess what we could do is this:
206
207 -- Execute V65C02 for one frame. The read/writes at I/O address $C030 fill up the buffer
208    to the current time position.
209 -- The sound callback function copies the pertinent area out of the buffer, resets
210    the time position back (or copies data down from what it took out)
211 */
212
213 void HandleBuffer(uint64 elapsedCycles)
214 {
215         // Step 1: Calculate delta time
216         uint64 deltaCycles = elapsedCycles - lastToggleCycles;
217
218         // Step 2: Calculate new buffer position
219         uint32 currentPos = (uint32)((double)deltaCycles / CYCLES_PER_SAMPLE);
220
221         // Step 3: Make sure there's room for it
222         // We need to lock since we touch both soundBuffer and soundBufferPos
223         SDL_mutexP(mutex2);
224         while ((soundBufferPos + currentPos) > (SOUND_BUFFER_SIZE - 1))
225         {
226                 SDL_mutexV(mutex2);                                             // Release it so sound thread can get it,
227                 SDL_mutexP(mutex);                                              // Must lock the mutex for the cond to work properly...
228                 SDL_CondWait(conditional, mutex);               // Sleep/wait for the sound thread
229                 SDL_mutexV(mutex);                                              // Must unlock the mutex for the cond to work properly...
230                 SDL_mutexP(mutex2);                                             // Re-lock it until we're done with it...
231         }
232
233         // Step 4: Backfill and adjust lastToggleCycles
234         // currentPos is position from "zero" or soundBufferPos...
235         currentPos += soundBufferPos;
236
237 #ifdef WRITE_OUT_WAVE
238         uint32 sbpSave = soundBufferPos;
239 #endif
240         // Backfill with current toggle state
241         while (soundBufferPos < currentPos)
242                 soundBuffer[soundBufferPos++] = (uint16)sample;
243
244 #ifdef WRITE_OUT_WAVE
245         fwrite(&soundBuffer[sbpSave], sizeof(int16), currentPos - sbpSave, fp);
246 #endif
247
248         SDL_mutexV(mutex2);
249         lastToggleCycles = elapsedCycles;
250 }
251
252 void ToggleSpeaker(uint64 elapsedCycles)
253 {
254         if (!soundInitialized)
255                 return;
256
257         HandleBuffer(elapsedCycles);
258         speakerState = !speakerState;
259         sample = (speakerState ? amplitude[ampPtr] : -amplitude[ampPtr]);
260 }
261
262 void AdjustLastToggleCycles(uint64 elapsedCycles)
263 {
264         if (!soundInitialized)
265                 return;
266 /*
267 BOOKKEEPING
268
269 We need to know the following:
270
271  o  Where in the sound buffer the base or "zero" time is
272  o  At what CPU timestamp the speaker was last toggled
273     NOTE: we keep things "right" by advancing this number every frame, even
274           if nothing happened! That way, we can keep track without having
275           to detect whether or not several frames have gone by without any
276           activity.
277
278 How to do it:
279
280 Every time the speaker is toggled, we move the base or "zero" time to the
281 current spot in the buffer. We also backfill the buffer up to that point with
282 the old toggle value. The next time the speaker is toggled, we measure the
283 difference in time between the last time it was toggled (the "zero") and now,
284 and repeat the cycle.
285
286 We handle dead spots by backfilling the buffer with the current toggle value
287 every frame--this way we don't have to worry about keeping current time and
288 crap like that. So, we have to move the "zero" the right amount, just like
289 in ToggleSpeaker(), and backfill only without toggling.
290 */
291         HandleBuffer(elapsedCycles);
292 }
293
294 void VolumeUp(void)
295 {
296         // Currently set for 8-bit samples
297         // Now 16
298         if (ampPtr < 16)
299                 ampPtr++;
300 }
301
302 void VolumeDown(void)
303 {
304         if (ampPtr > 0)
305                 ampPtr--;
306 }
307
308 uint8 GetVolume(void)
309 {
310         return ampPtr;
311 }
312
313 /*
314 HOW IT WORKS
315
316 the main thread adds the amount of cpu time elapsed to samplebase. togglespeaker uses
317 samplebase + current cpu time to find appropriate spot in buffer. it then fills the
318 buffer up to the current time with the old toggle value before flipping it. the sound
319 irq takes what it needs from the sound buffer and then adjusts both the buffer and
320 samplebase back the appropriate amount.
321
322
323 A better way might be as follows:
324
325 Keep timestamp array of speaker toggle times. In the sound routine, unpack as many as will
326 fit into the given buffer and keep going. Have the toggle function check to see if the
327 buffer is full, and if it is, way for a signal from the interrupt that there's room for
328 more. Can keep a circular buffer. Also, would need a timestamp buffer on the order of 2096
329 samples *in theory* could toggle each sample
330
331 Instead of a timestamp, just keep a delta. That way, don't need to deal with wrapping and
332 all that (though the timestamp could wrap--need to check into that)
333
334 Need to consider corner cases where a sound IRQ happens but no speaker toggle happened.
335
336 If (delta > SAMPLES_PER_FRAME) then
337
338 Here's the relevant cases:
339
340 delta < SAMPLES_PER_FRAME -> Change happened within this time frame, so change buffer
341 frame came and went, no change -> fill buffer with last value
342 How to detect: Have bool bufferWasTouched = true when ToggleSpeaker() is called.
343 Clear bufferWasTouched each frame.
344
345 Two major cases here:
346
347  o  Buffer is touched on current frame
348  o  Buffer is untouched on current frame
349
350 In the first case, it doesn't matter too much if the previous frame was touched or not,
351 we don't really care except in finding the correct spot in the buffer to put our change
352 in. In the second case, we need to tell the IRQ that nothing happened and to continue
353 to output the same value.
354
355 SO: How to synchronize the regular frame buffer with the IRQ buffer?
356
357 What happens:
358   Sound IRQ --> Every 1024 sample period (@ 44.1 KHz = 0.0232s)
359   Emulation --> Render a frame --> 1/60 sec --> 735 samples
360     --> sound buffer is filled
361
362 Since the emulation is faster than the SIRQ the sound buffer should fill up
363 prior to dumping it to the sound card.
364
365 Problem is this: If silence happens for a long time then ToggleSpeaker is never
366 called and the sound buffer has stale data; at least until soundBufferPos goes to
367 zero and stays there...
368
369 BUT this should be handled correctly by toggling the speaker value *after* filling
370 the sound buffer...
371
372 Still getting random clicks when running...
373 (This may be due to the lock/unlock sound happening in ToggleSpeaker()...)
374 */
375