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