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