I'm on a little programming journey right now that's taken me to some surprising places.
Where I started: I need to make some sound effects and music for my current game project.
Where I am today: Reading about why I should be calling pselect() instead of select() to sleep my event loop if I'm interrupting it with a signal handler, and implementing some extensions to my API for thread synchronization.
How I got from A to B:
• For a project I made several years ago, I used a port of sfxr made by a friend of mine, which only runs on Mac OS. I saved the parameter files from each effect so I could reimport and modify them in the same tool, and exported the result as Ogg Vorbis so I could load and play it in the game. So far so good.
• On my next project, the tool written by my friend didn't work as well anymore due to operating system updates breaking things. sfxr also had a lot of limitations. I switched to bfxr, which has a few more synthesis parameters and worked a little bit better overall, and kept doing pretty much the same thing.
• For unrelated reasons, I switched to Linux as my primary operating system, making it very inconvenient to have a Mac-only program anywhere in my workflow. Even if I was OK with getting out my old Mac in any event where I needed to re-export my sfxr audio files, there was the looming threat of losing access to the tool due to events outside my control, or at least having it become so inconvenient as to be impractical. I had similar longevity concerns about bfxr, and both tools had some data integrity problems causing me to lose some of my work. Not great.
• Conveniently, sfxr and bfxr are both open source, so I was able to take their audio synthesis functions and combine them together into a single utility program of my own making (porting the relevant parts of bfxr from ActionScript to C in the process), which builds and runs in all the same places as the games that use its output. Longevity problem solved, and any remaining data integrity problems could only be of my own making.
• Now that I have full control of my audio synthesis, I can start extending its capability to fit my needs that weren't fulfilled by sfxr and bfxr. I implemented a music sequencer in the tool which can use synthesized sounds as its instruments and composite them into a full song.
• While editing the synthesizer parameters of an instrument, I had the thought that it would be nice if I could update my waveform view live so I could see exactly what was happening to the output while dragging sliders. Resynthesizing the sample while dragging worked OK in some places, but the performance wasn't sufficient for longer ones to be done in real time. OK, I guess I need to spin synthesis off into a secondary thread so that the UI can keep updating while it works on the ones that take a bit too long. I'll signal to the synth thread that there's work to do when a parameter change is made, and then display its result whenever it signals back to me that it's done.
• Signaling to the thread is relatively straightforward - when I spawn it, it immediately blocks on a semaphore which I can post to whenever I want to request that it run the synthesis function. Once it's done with its work, however, I realized I didn't have a good mechanism for waking up the main thread with the notification that the waveform can be redisplayed. I added a new API function to my thread abstraction library to queue up a callback to be invoked from the main thread when it returns to the main event loop.
• This function need a platform-specific implementation for each system my framework supports, so I went off to write the Linux version of it first. The way my run loop works right now is that if there are no events pending or timers running, it sleeps until something it needs to know about happens. This was done with a call to select() on the socket used by X11 for event handling. A quick web search warned me away from trying to just post an XEvent from a secondary thread, so I needed a different communication mechanism that would be thread-safe.
• After some basic research, I ended up at
this Stack Overflow post which discusses more or less what I'm trying to do. I read the select() man page to try to understand better how it works so that I could interrupt it from another thread, and somehow ended up at
this article about pselect() and how it could be used to wait for both a file descriptor and a signal at the same time. Seems like what I want.
Then the slight absurdity of what I was doing hit me, and I went off to write this forum post to organize my thoughts and make sure it still seemed like what I was doing made sense. I haven't fully verified yet that using a signal to interrupt pselect() will work for my purposes, but so far it looks like the thing that I need.
This whole process of digging
all the way down to the bottom of what I need to know to solve a particular problem is always really satisfying. Even though it takes a while, I learn a lot, and understanding all of the nuances gives me confidence in the final implementation and the knowledge necessary to fix it when things go wrong. This kind of stuff is totally my jam.