As part of trying to regain some active affinity for my field (that having been in a difficult place lately for various reasons), I've been poking around at seeing if I can access SDL 2 from Common Lisp. The closest I've seen thus far is Gary Hollis's cl-sdl2 repository, but it's quite incomplete and has no license attached, and its design as far as putting Lispier interfaces on top is far enough from what I'd be looking for that I decided to explore building on CFFI myself instead.
One thing I've run into while translating declarations from the C header files is audio callbacks. Traditionally, SDL runs its audio processing on its own thread (or an unspecified thread, at least) and uses a callback-based approach similar to JACK or (from what I am told) Apple's Core Audio. The application is given an empty or full buffer for playback or capture respectively and is expected to provide or consume the audio data at the pleasure of the audio clock.
Now, like many current Lisp users, I'm running SBCL, and while CFFI-on-SBCL supports defcallback forms for making C-callable function pointers on many platforms, unfortunately, §13.9 of the SBCL manual states that running Lisp code is only supported on threads started from Lisp. So it's fine for synchronous callbacks, but we can't use a CFFI callback for the audio callback in that context. What alternatives come to mind?
Use the newer
SDL_QueueAudioandSDL_DequeueAudiofunctions that are intended to allow applications to doread/writestyle audio I/O from the main thread. This would be the most straightforward, but it feels instinctively iffy to me from an audio processing standpoint, and specifically I don't see a great way to get high-water/low-water notifications into the SDL event loop; the closest thing seems to be pollingSDL_GetQueuedAudioSize.Write a callback in C and have it signal a Lisp-created thread with the buffer pointer, which does the audio processing and then signals back. There are two big extra complexity sources here. One is portable synchronization, though we could potentially use the SDL threading functions themselves for that. The other is getting the callback into the running process in the first place; bringing in C build system considerations is the opposite of what I was hoping for with CFFI bindings. A variation of this would be to make an audio queue similar to (1) but add richer main-loop synchronization using
SDL_PushEvent.Switch to a CL implementation that allows calling into Lisp from foreign threads. This also has to be in a way that doesn't involve intense setup/teardown overhead or having to do something from the foreign thread prior, since I don't control the actual thread arrangement. It looks like Clozure CL might support this; the manual mentions “when native threads that aren't created by Clozure CL first call into lisp”, which implies that it's possible. ECL also seems like it might allow this, but its FFI and performance characteristics were somewhat weaker last I recall, and the manual didn't obviously state outright whether it would work. I vaguely recall having tried and failed to get Clozure CL to install on my machine before, so that could be its own rathole.
Same as (2) except rather than writing in C, I do jiggery pokery to assemble a small machine-code shunt from Lisp and use that as the callback function. This avoids the C build system issue at the cost of being absolutely wacko and also distinctly nonportable and close to unmaintainable—maybe less so if there's a GNU Lightning equivalent as a CL library somewhere?—but then syscalls, and then it all flies apart. Let's not do this (unless I decide it would make a good joke).
Give up on doing the audio processing in Lisp, leaving it with the role of the ‘scripting’ language directing a GStreamer effect graph or whatever. Awkward—it would be beneficial to pull in foreign audio library code in a more general sense (SDL_mixer is a common basic thing to get started with, and then a number of games use FMOD or the like), but losing most-to-all ability to deal with the sample data would be a pain for some use cases, and now we've potentially recreated the “how does the other code communicate with Lisp re the high-level disposition of playback/capture” on another level.
Give up and switch to C++. Not that I haven't done synthesis code in C++ before, but that's not what I'm after here—I'd lose my REPL, and take on heavyweight template juggling and…
Same as (6) but extend the implementation of the C++ language with all the Lisp stuff I want. Problem: then I fall into the dark void shockwave and never come out, and also I might as well design my dream language while I'm doing something like that, which idea leads to perhaps thrice as much falling into the dark void shockwave and never coming out. But flip that around, and does that idea just point in the direction of Clasp? I've never tried it before, but that might be another candidate to look at for (3) that would give me much easier C++ access along the way—which can be important in the gameish/interactive-media sort of space. One big disadvantage here would be that it doesn't currently support Windows.
I'm not sure what'll turn out to be best yet, but seeing if I can get Clozure CL to run seems like a reasonable next thing to try…