Skip to content
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

Question: dsp swap with longer cross-fade? #406

Open
avdrd opened this issue Jan 1, 2022 · 3 comments
Open

Question: dsp swap with longer cross-fade? #406

avdrd opened this issue Jan 1, 2022 · 3 comments

Comments

@avdrd
Copy link

avdrd commented Jan 1, 2022

The supported way to swap a dsp in extempore is to bind-func it again. I could not find a way to do this with a longer cross-fade though.

In some other systems like SuperCollider it's pretty easy to have a Ndef cross-fade to another when its source is changed; there's even a \fadeTime parameter in SC (well in JITLib, but it ships with SC) that controls that cross-fade and one can even supply their own fading envelope with a bit more work. The default one is S-shaped for audio, i.e. sine a sine from 0..pi/2 for the fade-in source and its mirror, i.e. pi/2..pi, for the source being faded-out. For control-rate sources, a linear envelope is automagically used instead. As you can see, a fair bit of thinking went into designing that in SC, so it mostly works as expected "out of the box".

So how can I get an equivalent functionality in extempore, at least in terms of controlling a basic \fadeTime length when doing a bind-func for a dsp that's already bound?

@digego
Copy link
Owner

digego commented Jan 3, 2022 via email

@avdrd
Copy link
Author

avdrd commented Jan 3, 2022

Fair enough. Even in SuperCollider such layering exists. The basic scsynth server doesn't quite have those notions of replacement with cross-fade, but only of node replacement, although it does have an XOut ugen that allows multiple "dsps" to effectively give the impression of running on the same "dsp slot", or rather on the same (share) bus in scsynth's case. I see Extempore doesn't seem to have this notion of shared busses at the lowest layer. I'm guessing it's not not hard to add such a shared-bus notion that's basically an array of functions all piping through the same dsp. I'm just a bit surprised that nobody using Extempore need this yet, i.e. I'd have expected you to point me to some Extempore library that does this. 🧀

@monkey-w1n5t0n
Copy link

I agree with Andrew; this comparison is not just unfair, it's impractical and unhelpful. bind-func is XTLang's low-level way of (re)compiling a function and binding it to a name (any function, not just DSP ones), while SuperCollider's JITlib is an entire library built on top of the fixed scsynth architecture that doesn't need to recompile anything (at least not to machine code, it compiles a SynthDef to a bytecode format that is then interpreted).

That being said, the crossfade functionality you're talking about is certainly both useful and possible; here's one way it could perhaps work:

Let's assume we have a (bind-func foo ...) which makes a sound. If we want to change its definition, recompile, and have the two sounds crossfade before the old is fully replaced by the new, then we need a few things:

  1. A "mixer" to handle the actual crossfade by mixing the two sounds. This will of course need to be another function, which will need to know when compilation of the new version has finished and it's ready to be called, and should also be able to free the old function after the crossfade has completed (otherwise we end up with an ever-growing "graveyard" of old functions in memory that we don't use anymore).
  2. Therefore, the mixer needs both versions of foo, old and new, to be available for the full duration of the crossfade. This means that it would need to keep some kind of distinct reference to both, otherwise it wouldn't be able to distinguish between them while they're both around. Perhaps they could be compiled anonymously as lambdas and stored as closure pointers inside current and new parameters of the mixer closure?
  3. But if the functions themselves replace one another, then how do other functions that are using foo for their own sound know that there has been a new version and that they should start listening to that instead? Well then, maybe foo should have been the name of the mixer all along, not the function(s). This way, other functions never have to be "updated" about any change, because from their perspective there isn't any: they only ever listen to the mixer's output. When there is no crossfade, the mixer simply returns the values from the latest lambda.
  4. But then the mixer does need to be updated: it needs to be provided the new lambda and be told to compile it and start the crossfade, with an optional argument as to how long that crossfade should be. So bind-func won't work for us here, because that would just replace the mixer object with a brand-new mixer.

We therefore need a new macro, which could be something like (bind-macro (bind-node name fade-time lambda) ...). When called, it would first check to see whether a mixer already exists under that name. If it doesn't (i.e. this is the first time we've introduced a node by the name foo), then it would create the mixer using bind-func and initialise it with the lambda passed in. If it does (i.e. we're re-defining the function that produces foo's sound), then it would just pass it the new lambda and ask it to start crossfading.

This sounds like a fun little project so I may take a stab at it soon. One thing I'm not sure about, is the call to bind-func synchronous? Also, is there a way to check whether a function exists under a certain name, e.g. something like (is-bound-func 'foo)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants