Well, from what I've studied from the code here is my analysis:
There are two threads executing:
Thread N1: This is the 'normal' thread, the one that indeed emulates the machine.
Thread N2: This one is created by Thread N1 when SDL_OpenAudio() is called. It writes the buffer to the hardware and calls the callback function (the function is soundCallback()).
I debugged the code through this function and, apparently, there is no thread with a higher priority; I'm not sure if this is an option passed by the user with pthreads, or if one of the threads get more ticks to execute. The thing is that when debugging both threads alternate execution and it appears to be no 'privileged' thread.
Three semaphores are created by Thread N1 (this is before creating Thread N2):
sdlBufferLock = SDL_CreateSemaphore(1);
sdlBufferFull = SDL_CreateSemaphore(0);
sdlBufferEmpty = SDL_CreateSemaphore(1);
Thread N2 is created and it calls soundCallback(). It executes the following:
Because this semaphore was initialized to 0, Thread N2 blocks until the buffer is filled (until Thread N1 calls SDL_SemPost(sdlBufferFull).
Now, with Thread N2 sleeping, Thread N1 use a counter to determine when the buffers must be written. When the sample rate is 22khz, the counter is 48. The counter is clocked by the main CPU clock; that is, the counter is decremented with the number of ticks of the executing instruction. When the counter is equal or less than 0, only one sample is written to the buffer, so the buffer is filled after various of this loops.
When the buffer is filled, Thread N1 executes the following (Thread N2 is still sleeping):
Because sdlBufferEmpty was initialized to 1, the thread doesn't sleep. Next it fills the last portion of the buffer, and then:
SDL_SemPost(sdlBufferFull); // Thread N2 is woken up.
Now, Thread N1 executes again SDL_SemWait(sdlBufferEmpty), so it goes to sleep.
Now Thread N2 is the only one executing. It does the following:
Copies the buffer to the stream set up by SDL that is going to be written to the hardware. It then executes SDL_SemPost(sdlBufferEmpty), so Thread N1 is woken up.
Next, Thread N2 returns from the callback function, writes the buffer to the hardware, and calls the callback function again.
One of my conjectures was that the timing we are discussing was accomplished when Thread N1 has to wait until Thread N2 dumps the buffer to the hardware (after all, Thread N1 has to wait Thread N2, and Thread N2 does a write() to the sound hardware, so I guess this takes some time). I thinks this because I noticed that when the basic loop just described (the synchronization between the threads with the semaphores) is not executed, the emulator goes way too fast (as if there were no timing at all). So I suppose the timing is done here, and not through frame skipping.
But, if this is so, I can't figure out how the correct timing is achieved in this way. I mean, the sound counter set to 48, and the waiting for a write() system call, How is this related to the real 60-frame-per-second Gameboy timing? Although I must say that when I compiled and ran VisualBoy with this apparent timing it was a bit too fast, so I can't figure out how the task of 'going the right speed' is accomplished.
If any ideas, I'd really appreciate it.