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