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]
20 // - Figure out why it's playing too fast
25 #include <string.h> // For memset, memcpy
32 //#define WRITE_OUT_WAVE
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
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 ???
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)
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 };
78 static FILE * fp = NULL;
81 // Private function prototypes
83 static void SDLSoundCallback(void * userdata, Uint8 * buffer, int length);
87 // Initialize the SDL sound system
92 // To weed out problems for now...
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)...
99 desired.samples = 512; // Let's try a 1/2K buffer (can always go lower)
100 desired.callback = SDLSoundCallback;
102 device = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, 0);
106 WriteLog("Sound: Failed to initialize SDL sound.\n");
110 conditional = SDL_CreateCond();
111 mutex = SDL_CreateMutex();
112 mutex2 = SDL_CreateMutex();// Let's try real signalling...
114 lastToggleCycles = 0;
115 sample = desired.silence; // ? wilwok ? yes
117 SDL_PauseAudioDevice(device, 0); // Start playback!
118 soundInitialized = true;
119 WriteLog("Sound: Successfully initialized.\n");
121 #ifdef WRITE_OUT_WAVE
122 fp = fopen("./apple2.wav", "wb");
128 // Close down the SDL sound subsystem
132 if (soundInitialized)
134 // SDL_PauseAudio(true);
135 SDL_PauseAudioDevice(device, 1);
137 SDL_CloseAudioDevice(device);
138 SDL_DestroyCond(conditional);
139 SDL_DestroyMutex(mutex);
140 SDL_DestroyMutex(mutex2);
141 WriteLog("Sound: Done.\n");
143 #ifdef WRITE_OUT_WAVE
150 void SoundPause(void)
152 if (soundInitialized)
153 SDL_PauseAudioDevice(device, 1);
157 void SoundResume(void)
159 if (soundInitialized)
160 SDL_PauseAudioDevice(device, 0);
165 // Sound card callback handler
167 static void SDLSoundCallback(void * /*userdata*/, Uint8 * buffer8, int length8)
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...)
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);
182 // Recast this as a 16-bit type...
183 int16_t * buffer = (int16_t *)buffer8;
184 uint32_t length = (uint32_t)length8 / 2;
186 //WriteLog("SDLSoundCallback(): filling buffer...\n");
187 if (soundBufferPos < length) // The sound buffer is starved...
189 for(uint32_t i=0; i<soundBufferPos; i++)
190 buffer[i] = soundBuffer[i];
192 // Fill buffer with last value
193 // memset(buffer + soundBufferPos, (uint8_t)sample, length - soundBufferPos);
194 for(uint32_t i=soundBufferPos; i<length; i++)
197 soundBufferPos = 0; // Reset soundBufferPos to start of buffer...
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];
206 soundBufferPos -= length;
208 // Move current buffer down to start
209 for(uint32_t i=0; i<soundBufferPos; i++)
210 soundBuffer[i] = soundBuffer[length + i];
214 //WriteLog("SDLSoundCallback(): SDL_mutexV(mutex2)\n");
216 // Wake up any threads waiting for the buffer to drain...
217 SDL_CondSignal(conditional);
218 //WriteLog("SDLSoundCallback(): end\n");
222 // This is called by the main CPU thread every ~21.333 cycles.
223 void WriteSampleToBuffer(void)
225 //WriteLog("WriteSampleToBuffer(): SDL_mutexP(mutex2)\n");
228 // This should almost never happen, but...
229 while (soundBufferPos >= (SOUND_BUFFER_SIZE - 1))
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...
239 soundBuffer[soundBufferPos++] = sample;
240 //WriteLog("WriteSampleToBuffer(): SDL_mutexV(mutex2)\n");
245 // Need some interface functions here to take care of flipping the
246 // waveform at the correct time in the sound stream...
249 Maybe set up a buffer 1 frame long (44100 / 60 = 735 bytes per frame)
251 Hmm. That's smaller than the sound buffer 2048 bytes... (About 2.75 frames needed to fill)
253 So... I guess what we could do is this:
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)
261 void HandleBuffer(uint64_t elapsedCycles)
263 // Step 1: Calculate delta time
264 uint64_t deltaCycles = elapsedCycles - lastToggleCycles;
266 // Step 2: Calculate new buffer position
267 uint32_t currentPos = (uint32_t)((double)deltaCycles / CYCLES_PER_SAMPLE);
269 // Step 3: Make sure there's room for it
270 // We need to lock since we touch both soundBuffer and soundBufferPos
273 while ((soundBufferPos + currentPos) > (SOUND_BUFFER_SIZE - 1))
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...
282 // Step 4: Backfill and adjust lastToggleCycles
283 // currentPos is position from "zero" or soundBufferPos...
284 currentPos += soundBufferPos;
286 #ifdef WRITE_OUT_WAVE
287 uint32_t sbpSave = soundBufferPos;
289 // Backfill with current toggle state
290 while (soundBufferPos < currentPos)
291 soundBuffer[soundBufferPos++] = sample;
293 #ifdef WRITE_OUT_WAVE
294 fwrite(&soundBuffer[sbpSave], sizeof(int16_t), currentPos - sbpSave, fp);
298 lastToggleCycles = elapsedCycles;
302 void ToggleSpeaker(uint64_t elapsedCycles)
304 if (!soundInitialized)
307 // HandleBuffer(elapsedCycles);
308 speakerState = !speakerState;
309 sample = (speakerState ? amplitude[ampPtr] : -amplitude[ampPtr]);
313 void AdjustLastToggleCycles(uint64_t elapsedCycles)
315 if (!soundInitialized)
320 We need to know the following:
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
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.
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.
342 HandleBuffer(elapsedCycles);
348 // Currently set for 16-bit samples
354 void VolumeDown(void)
361 uint8_t GetVolume(void)
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.
376 A better way might be as follows:
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
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)
387 Need to consider corner cases where a sound IRQ happens but no speaker toggle happened.
389 If (delta > SAMPLES_PER_FRAME) then
391 Here's the relevant cases:
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.
398 Two major cases here:
400 o Buffer is touched on current frame
401 o Buffer is untouched on current frame
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.
408 SO: How to synchronize the regular frame buffer with the IRQ buffer?
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
415 Since the emulation is faster than the SIRQ the sound buffer should fill up
416 prior to dumping it to the sound card.
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...
422 BUT this should be handled correctly by toggling the speaker value *after* filling
425 Still getting random clicks when running...
426 (This may be due to the lock/unlock sound happening in ToggleSpeaker()...)