flucoma / flucoma-sc

Fluid Corpus Manipulation plugins for Supercollider
BSD 3-Clause "New" or "Revised" License
70 stars 16 forks source link

Obstacles to Supernova support / SC API lacunae #90

Open weefuzzy opened 2 years ago

weefuzzy commented 2 years ago

We've had a couple of requests for Supernova support (see #82 but also elsewhere, by email etc., and a mention here https://github.com/supercollider/supercollider/issues/5347#issuecomment-1034733698). I was asked by @dyfer to actually write this up properly and publicly (some weeks ago, sorry).

The short version is that we can't reasonably do it without some additions to the SC PlugIn API: nasty hacks that we currently do for scsynth are untenable for Supernova. These are:

  1. Being able to SendReply from the NRT command thread, including sending return data
  2. (by extension) a way of copying and cleaning up void* pointers to ReplyAddr (or Supernova equivalent) without needing to concern ourselves with what they are
  3. a counterpart to fBufAlloc to also free SndBuffer data
    • There should, ideally, be an API function to copy a SndBuffer struct, because what works in scsynth (*a = *b). doesn't work in supernova, and frankly, I don't think plugin authors should be burdened with that knowledge)

At the 'and a pony too' end of the list, I'd also like there to be better support for Plugins to invoke and test for the existence of PluginCmds, but that's more to do with what I'd like to be able to do in the future

As it stands, the brittle workaround for these lacunae in scsynth is to commit a code crime and just link to a bunch of internals that I have no business knowing or caring about. This then breaks if our plugins are built against a different SC (or Boost) to what's running. Not ideal but (I stress), not a decision made lightly.


The SendReply stuff

The core issue is that a lot of FluCoMa isn't real-time UGen plugins, which are the typical use for the API.

This means that a lot of our time is spent in the NRT command thread, via the AsyncPluginCmd thread, and rely on being able to respond to messages from the client with actual data (rather than just \done). To further complicate matters, many of our processors offer the option of delegating long-running computation to a worker thread so that the command FIFO doesn't get choked and unresponsive.

I guess a first question someone might ask is why not? I.e. why not just try and make these things 'normal' RT plugins and use UnitCmds to build a message interface? This was our original approach, but it ended up unworkable:

This underpins the motivation for wanting a NRT SendReply available in the API (and, by extension, the ability to copy and destroy the inReply pointers that get passed to PluginCmds). With this in place we are able to provide an interface that is robust and works as expected in the language. To make this happen in the current version, the Bad Things (i.e. stuff that pulls in ReplyAddr) are sandboxed away in a static library that provides the functions needed.

void* copyReplyAddress(void* inreply);
void deleteReplyAddress(void* inreply);
void SendReply(void* inReplyAddr, char* inBuf, int inSize);

This happens to work for scsynth, but I don't think can be made to work for supernova: the classes involved in supernova are (very) different, and much more buried in the details. The end result seemed to be that it would involve building the whole of supernova as a static library, and even then felt decidedly more iffy (from a pretty iffy starting point).

FWIW, I think the absence of these from the InterfaceTable is more an oversight than anything else. As far as I can tell, no-one has needed to push the AsyncPluginCmd stuff so hard before, although I understand that the SC VST project would also benefit from this. I think the approach taken there is to instead use Buffer as a hacky way to 'transmit' return data back to clients. I guess, if we knew that the above changes were never going to happen, then we could try and fall back to this, although I can't see how it works without imposing at least one additional round-trip between client and server to get the buffer data.

Buffer freeing and copying

This is much simpler! The InterfaceTable has a fBufAlloc but no corresponding free. Again, this feels like something missing because nobody needed it: if you use the BufGen facilities, then this takes care of freeing old buffers for you. However, this is limited to Buffer processing that involves a single output buffer, whereas we have a number of cases that return multiple buffers. As such, we need to manage their cleanup ourselves. The allocations involved are actually aligned, and use Boost to handle cross-platform horribleness. So, again, we do a code crime, and pull in the corresponding aligned free to ensure that stuff behaves.

Again, it works very well, code crime notwithstanding.

The issue with copying SndBuf instances in Supernova is down to how locks are added to the struct. Basically, this changes it from being POD-ish to being a non-copyable class. As such, the only way to 'copy' one in supernova is to go through field-by-field. Given that supernova itself has code to do this, then a more robust alternative to duplicating this for plugin code would be for there to be a function that worked for both scsynth and supernova, relieving plugin authors of needing to know or care about the internal details of SndBuf.


Now, unfortunately, one can't just wave one's paw and have the SC API magically extended. There is already active discussion over at SC on whether / if API changes could be enacted at all, or at least in a way that minimizes ABI-breakage chaos. I get the distinct impression that some community members would be uneasy with the prospect of adding new stuff to the API, on the sound basis of resisting function creep (although I think all of these are oversights that complete existing functionality rather than introducing new stuff).

Those discussions on the SC seem quite on and off, as people have bigger fish to fry. If they do decide to proceed, however, we could certainly offer to help out, whilst I'm still being paid to work on this.