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