-
Notifications
You must be signed in to change notification settings - Fork 48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Threading and overrun/underrun #72
base: master
Are you sure you want to change the base?
Conversation
…tly. Joined them back together for better FPS management. The write == read test to coordinate the LCD and MCU threads also didn't work if your not generating samples fast enough to not underrun the audio buffer.
My understanding of the current implementation is that the MCU tries to fill the sample buffer until it's full. After that, there's no work to do so it unlocks and yields so the main thread can render the LCD. I don't think you can remove the mutex because the MCU writes LCD state, and if it's not synchronized you might observe invalid state while rendering.
I think there are two bugs in the current code. First, it doesn't distinguish between an empty buffer and a full buffer. In a proper ringbuffer the write head must always be ahead of the read head. When the program starts, the buffer is empty ( I assume the author meant for the MCU to unlock here when the buffer is full because it doesn't make sense to stop running the emulator when the buffer is empty - the audio thread is starved for samples. I tried to fix this in #68 by instead checking if the ringbuffer is full using The second bug is that |
That’s also how I inferred the intended behavior should be. But again that makes one thread dependent on the other. Instead of releasing the mutex in the one place where the buffer is full, you could instead just call the LCD update function there. It the desire is to have separate logical threads to model the hardware, I would suggest maybe a co-thread instead, a la bsnes.
For whatever reason on my machine nuked doesn’t generate near enough samples per callback to fill the time, an underrun. So I observe that the read pointer marches forward at a steady pace, sometimes lapping the write pointer in the buffer. Because the write pointer is so far behind the read pointer those samples aren’t heard until the read pointer wraps around to vaild data again. The experience is a chugging in sound output as I get a stretch of good data then 0ed data. With my patch I’m only feeding the callback as much data as is available so the read pointer can never pass the write pointer. The experience is now a crackling sound as the samples are being played as they're available. |
This PR isn't meant to be taken as is. It's to generate a conversation about architecture. Currently the MCU and LCD main loops are both wholly constrained by the work thread mutex. So there is no threading benefit as neither can run while the other has the mutex. I haven't deep dived the LCD loop yet, but in this PR, I've done a more classical approach to updating visuals on a 60 fps time scale.
Also the read/write pointers aren't managed and the current setup only works if you generate exactly as many samples as you need for the callback each time. That doesn't work on my machine and probably a lot of others. This PR reports the over/under runs but the output is very crackly as there's not enough samples to fill the time.