5 // (C) 2005 Underground Software
7 // JLH = James L. Hammons <jlhamm@acm.org>
10 // --- ---------- ------------------------------------------------------------
11 // JLH 12/02/2005 Fixed a problem with sound callback thread signaling the
13 // JLH 12/03/2005 Fixed sound callback dropping samples when the sample buffer
14 // is shorter than the callback sample buffer
19 // - Figure out why it's losing samples (Bard's Tale) [DONE]
24 #include <string.h> // For memset, memcpy
29 //#define SAMPLE_RATE (44100.0)
30 #define SAMPLE_RATE (48000.0)
31 #define SAMPLES_PER_FRAME (SAMPLE_RATE / 60.0)
32 #define CYCLES_PER_SAMPLE (1024000.0 / SAMPLE_RATE)
33 #define SOUND_BUFFER_SIZE (8192)
34 //#define AMPLITUDE (16) // -32 - +32 seems to be plenty loud!
41 static SDL_AudioSpec desired;
42 static bool soundInitialized = false;
43 static bool speakerState = false;
44 static uint8 soundBuffer[SOUND_BUFFER_SIZE];
45 static uint32 soundBufferPos;
46 static uint32 sampleBase;
47 static uint64 lastToggleCycles;
48 static uint64 samplePosition;
49 static SDL_cond * conditional = NULL;
50 static SDL_mutex * mutex = NULL;
51 static SDL_mutex * mutex2 = NULL;
52 static uint8 ampPtr = 5;
53 static uint16 amplitude[17] = { 0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048,
54 4096, 8192, 16384, 32768 };
56 // Private function prototypes
58 static void SDLSoundCallback(void * userdata, Uint8 * buffer, int length);
61 // Initialize the SDL sound system
66 // To weed out problems for now...
70 desired.freq = SAMPLE_RATE; // SDL will do conversion on the fly, if it can't get the exact rate. Nice!
71 desired.format = AUDIO_S8; // This uses the native endian (for portability)...
72 // desired.format = AUDIO_S16SYS; // This uses the native endian (for portability)...
74 // desired.samples = 4096; // Let's try a 4K buffer (can always go lower)
75 // desired.samples = 2048; // Let's try a 2K buffer (can always go lower)
76 desired.samples = 1024; // Let's try a 1K buffer (can always go lower)
77 desired.callback = SDLSoundCallback;
79 if (SDL_OpenAudio(&desired, NULL) < 0) // NULL means SDL guarantees what we want
81 WriteLog("Sound: Failed to initialize SDL sound.\n");
85 conditional = SDL_CreateCond();
86 mutex = SDL_CreateMutex();
87 mutex2 = SDL_CreateMutex();// Let's try real signalling...
88 SDL_mutexP(mutex); // Must lock the mutex for the cond to work properly...
94 SDL_PauseAudio(false); // Start playback!
95 soundInitialized = true;
96 WriteLog("Sound: Successfully initialized.\n");
100 // Close down the SDL sound subsystem
104 if (soundInitialized)
106 SDL_PauseAudio(true);
108 SDL_DestroyCond(conditional);
109 SDL_DestroyMutex(mutex);
110 SDL_DestroyMutex(mutex2);
111 WriteLog("Sound: Done.\n");
116 // Sound card callback handler
118 static void SDLSoundCallback(void * userdata, Uint8 * buffer, int length)
120 // The sound buffer should only starve when starting which will cause it to
121 // lag behind the emulation at most by around 1 frame...
122 // (Actually, this should never happen since we fill the buffer beforehand.)
123 // (But, then again, if the sound hasn't been toggled for a while, then this
124 // makes perfect sense as the buffer won't have been filled at all!)
126 // Let's try using a mutex for shared resource consumption...
129 if (soundBufferPos < (uint32)length) // The sound buffer is starved...
131 //printf("Sound buffer starved!\n");
133 for(uint32 i=0; i<soundBufferPos; i++)
134 buffer[i] = soundBuffer[i];
136 // Fill buffer with last value
137 memset(buffer + soundBufferPos, (uint8)(speakerState ? amplitude[ampPtr] : -amplitude[ampPtr]), length - soundBufferPos);
138 soundBufferPos = 0; // Reset soundBufferPos to start of buffer...
139 sampleBase = 0; // & sampleBase...
140 //Ick. This should never happen!
141 //Actually, this probably happens a lot. (?)
142 // SDL_CondSignal(conditional); // Wake up any threads waiting for the buffer to drain...
143 // return; // & bail!
147 // Fill sound buffer with frame buffered sound
148 memcpy(buffer, soundBuffer, length);
149 soundBufferPos -= length;
150 sampleBase -= length;
152 // Move current buffer down to start
153 for(uint32 i=0; i<soundBufferPos; i++)
154 soundBuffer[i] = soundBuffer[length + i];
157 // Update our sample position
158 samplePosition += length;
161 // Wake up any threads waiting for the buffer to drain...
162 SDL_CondSignal(conditional);
165 // Need some interface functions here to take care of flipping the
166 // waveform at the correct time in the sound stream...
169 Maybe set up a buffer 1 frame long (44100 / 60 = 735 bytes per frame)
171 Hmm. That's smaller than the sound buffer 2048 bytes... (About 2.75 frames needed to fill)
173 So... I guess what we could do is this:
175 -- Execute V65C02 for one frame. The read/writes at I/O address $C030 fill up the buffer
176 to the current time position.
177 -- The sound callback function copies the pertinent area out of the buffer, resets
178 the time position back (or copies data down from what it took out)
181 void ToggleSpeaker(uint64 elapsedCycles)
183 if (!soundInitialized)
186 uint64 deltaCycles = elapsedCycles - lastToggleCycles;
189 if (time > 95085)//(time & 0x80000000)
191 WriteLog("ToggleSpeaker() given bad time value: %08X (%u)!\n", time, time);
196 // 1.024 MHz / 60 = 17066.6... cycles (23.2199 cycles per sample)
197 // Need the last frame position in order to calculate correctly...
202 // uint32 currentPos = sampleBase + (uint32)((double)elapsedCycles / CYCLES_PER_SAMPLE);
203 uint32 currentPos = (uint32)((double)deltaCycles / CYCLES_PER_SAMPLE);
208 ______ | ______________ | ______
211 Speaker is toggled, then not toggled for a while. How to find buffer position in the
214 IRQ buffer len is 1024.
216 Could check current CPU clock, take delta. If delta > 1024, then ...
218 Could add # of cycles in IRQ to lastToggleCycles, then currentPos will be guaranteed
219 to fall within acceptable limits.
223 if (currentPos > SOUND_BUFFER_SIZE - 1)
226 WriteLog("ToggleSpeaker() about to go into spinlock at time: %08X (%u) (sampleBase=%u)!\n", time, time, sampleBase);
228 // Still hanging on this spinlock...
229 // That could be because the "time" value is too high and so the buffer will NEVER be
231 // Now that we're using a conditional, it seems to be working OK--though not perfectly...
233 ToggleSpeaker() about to go into spinlock at time: 00004011 (16401) (sampleBase=3504)!
234 16401 -> 706 samples, 3504 + 706 = 4210
236 And it still thrashed the sound even though it didn't run into a spinlock...
238 Seems like it's OK now that I've fixed the buffer-less-than-length bug...
240 // SDL_UnlockAudio();
241 // SDL_CondWait(conditional, mutex);
244 SDL_mutexV(mutex2);//Release it so sound thread can get it,
245 SDL_CondWait(conditional, mutex);//Sleep/wait for the sound thread
246 SDL_mutexP(mutex2);//Re-lock it until we're done with it...
248 currentPos = sampleBase + (uint32)((double)elapsedCycles / CYCLES_PER_SAMPLE);
250 WriteLog("--> after spinlock (sampleBase=%u)...\n", sampleBase);
254 int8 sample = (speakerState ? amplitude[ampPtr] : -amplitude[ampPtr]);
256 while (soundBufferPos < currentPos)
257 soundBuffer[soundBufferPos++] = (uint8)sample;
259 // This is done *after* in case the buffer had a long dead spot (I think...)
260 speakerState = !speakerState;
262 // SDL_UnlockAudio();
265 void AddToSoundTimeBase(uint32 cycles)
267 if (!soundInitialized)
272 sampleBase += (uint32)((double)cycles / CYCLES_PER_SAMPLE);
274 // SDL_UnlockAudio();
279 // Currently set for 8-bit samples
284 void VolumeDown(void)
290 uint8 GetVolume(void)
298 the main thread adds the amount of cpu time elapsed to samplebase. togglespeaker uses
299 samplebase + current cpu time to find appropriate spot in buffer. it then fills the
300 buffer up to the current time with the old toggle value before flipping it. the sound
301 irq takes what it needs from the sound buffer and then adjusts both the buffer and
302 samplebase back the appropriate amount.
305 A better way might be as follows:
307 Keep timestamp array of speaker toggle times. In the sound routine, unpack as many as will
308 fit into the given buffer and keep going. Have the toggle function check to see if the
309 buffer is full, and if it is, way for a signal from the interrupt that there's room for
310 more. Can keep a circular buffer. Also, would need a timestamp buffer on the order of 2096
311 samples *in theory* could toggle each sample
313 Instead of a timestamp, just keep a delta. That way, don't need to deal with wrapping and
314 all that (though the timestamp could wrap--need to check into that)
316 Need to consider corner cases where a sound IRQ happens but no speaker toggle happened.
318 If (delta > SAMPLES_PER_FRAME) then
320 Here's the relevant cases:
322 delta < SAMPLES_PER_FRAME -> Change happened within this time frame, so change buffer
323 frame came and went, no change -> fill buffer with last value
324 How to detect: Have bool bufferWasTouched = true when ToggleSpeaker() is called.
325 Clear bufferWasTouched each frame.
327 Two major cases here:
329 o Buffer is touched on current frame
330 o Buffer is untouched on current frame
332 In the first case, it doesn't matter too much if the previous frame was touched or not,
333 we don't really care except in finding the correct spot in the buffer to put our change
334 in. In the second case, we need to tell the IRQ that nothing happened and to continue
335 to output the same value.
337 SO: How to synchronize the regular frame buffer with the IRQ buffer?
340 Sound IRQ --> Every 1024 sample period (@ 44.1 KHz = 0.0232s)
341 Emulation --> Render a frame --> 1/60 sec --> 735 samples
342 --> sound buffer is filled
344 Since the emulation is faster than the SIRQ the sound buffer should fill up
345 prior to dumping it to the sound card.
347 Problem is this: If silence happens for a long time then ToggleSpeaker is never
348 called and the sound buffer has stale data; at least until soundBufferPos goes to
349 zero and stays there...
351 BUT this should be handled correctly by toggling the speaker value *after* filling
354 Still getting random clicks when running...
355 (This may be due to the lock/unlock sound happening in ToggleSpeaker()...)