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

Global send #77

Closed
wants to merge 11 commits into from
Closed

Global send #77

wants to merge 11 commits into from

Conversation

telephon
Copy link
Contributor

This PR is for discussing how a modified effects routing might look like. Thanky to @bgold-cosmos !

Ben Gold and others added 9 commits August 22, 2017 22:01
Demonstrates a new behavior for global effects with two features
  * any sound can independently control the "send" amount to an effect
  * other events that don't use the global effect won't interfere with the effect's parameters

This should make it a little easier to work with multiple streams to a single orbit when global effects are used.

This first commit demonstrates the idea with two new global effects: a tape delay and a gated reverb.

Example use in Tidal:
```
d1
 $ s "bd <~ ~ ~ cr> sn:2 <~ ~ ~ cr>" # release 1 # cut 1
 # gateverb 4 # gateverbd 0.5 # gateverbg 0.3 # gateverbr 1
 # gain 0.8

d3
 $ superimpose (### [bandf "[400,800,1300]", bandq 5])
 $ s "arpy*8" # up ("<0 0 3 -5>") # shape 0.2 # gain 0.8
 # tape 0.5 # taped 1 # tapefb 0.3 |*| speed 4
```
The reverb (using `room` and `size`) is now also a "send"-style effect, where `room` is the send amount.
`dry` has also been repurposed - it now controls the amount of dry sound in the final mix.  A zero value means you won't hear the original sound, but you can still use the various "send" parameters (like `room` `tape` `gateverb`) to get a processed "wet" sound.
Now all the global effects use sends, and should not be affected by other events on the same orbit that don't use effect parameters.
Tweaked GlobalDirtEffect.set so that unspecified (nil value) parameters are ignored when checking to see if a value has changed.
The fix in the previous commit has made the ugly workaround redundant.
@bgold-cosmos
Copy link
Contributor

I'll try to provide an overview/summary of how things are meant to work

  • For each global effect there is a "send" parameter that controls how much of the signal goes to the global effect. This is what you could call a "local" parameter - like most Tidal parameters it can be different for every sound, even if several sounds are playing simultaneously. Usually the "send" parameter is fairly obvious, delay for the delay send, room for reverb, leslie for the leslie, etc.
  • Global effects usually have other parameters (like size on the reverb) that are "global" parameters - it doesn't make sense or isn't feasible for simultaneous sounds to use different values. If an event occurs which doesn't specify one of these parameters, the effect should just keep using the last specified value.
  • There is a dry parameter which controls the amount of dry sound in the final mix. It's much like a send parameter to an effect that doesn't alter the sound. It defaults to 1.

This is implemented by giving every global effect has its own input bus, and the dirt_gate synth handles sending out signals to all the buses using the various "send" parameters.

@bgold-cosmos
Copy link
Contributor

Issues that could use work:

  • Right now it's still pretty tedious to add a new global effect in this way, since the new buses need to be added in several places in the Orbit code, dirt_gate needs to updated, sendGateSynth needs a corresponding update, etc.
  • There's some issue with parameters updating where sometimes the first time you use an effect it doesn't kick in until the second event. I'm not sure if this is DirtPause-related or something to do with the timing of DirtOrbit set messages.

@telephon
Copy link
Contributor Author

just two questions back:

. If an event occurs which doesn't specify one of these parameters, the effect should just keep using the last specified value.

This will lead to hanging effects, where the code doesn't reflect the sound you hear. Maybe convenient, but really?

For each global effect there is a "send" parameter that controls how much of the signal goes to the global effect

This is the case now, too, isn't it?

My main question however is: how do you specify which effects will be each others' inputs? Usually, e.g. you want the leslie go into the room delay, don't you?

@fdragovic
Copy link

fdragovic commented Feb 14, 2018 via email

@bgold-cosmos
Copy link
Contributor

This will lead to hanging effects, where the code doesn't reflect the sound you hear. Maybe convenient, but really?

One of my classic test cases is something like this:

d1 $ s "bd" # delay 1 # delaytime 0.02 # delayfb 0.9

d2 $ s "~ cp"

In "standard" SuperDirt, the "cp" winds up resetting the delay unit and so the "bd" cuts off unexpectedly. If you change to d2 $ s "cp" then both events occur simultaneously and the behavior is completely unpredictable. With the new behavior, the "bd" goes to the delay unit with the specified settings and d2 won't interfere with it (unless you specify delaytime or delayfeedback.

This does lead to situations where if you change the first part to just d1 $ s "bd" # delay 1 then the delay unit will keep using delayfeedback 0.9 and you have no record of it, so that is weird. But I think it's more usable because of the behavior above.

This is the case now, too, isn't it?

Not exactly, for example the room parameter was "global" (used within the reverb effect), which meant if you did d1 $ stack [s "bd" # room 1, s "cp" # room 0] the result was unpredictable. You might get reverb on both or reverb on neither, but you're definitely not going to get reverb on only one.

My main question however is: how do you specify which effects will be each others' inputs? Usually, e.g. you want the leslie go into the room delay, don't you?

I have not thought about this at all - I was thinking of the effects as being completely independent (but you're right, I don't think this was previously the case). If each effect now gets its own bus, I think they are pretty much independent - I don't know if that's desirable.

@bgold-cosmos
Copy link
Contributor

re: first question it might be worth doing this for when at least one synth is sending something and then revert back to default if nothing at all is sending a value

I think the problem is that I don't know a foolproof way of defining "nothing at all is sending a value". The tape delay has feedback and up to an eight-second buffer; I quite often do something like

d1
 $ superimpose (### [bandf "[400,800,1300]", bandq 5])
 $ s "arpy*8" # up ("<0 0 3 -5>") # shape 0.2 # gain 0.8
 # tape 0.5 # taped 1 # tapefb 0.6 |*| speed 4

d2
 $ some other stuff

and then silence d1. In that case, though, I want the tape unit to keep looping and making its echoes for perhaps 20 s, even though I'm not sending any events to it.

Perhaps just waiting until the effect itself is completely silent? Maybe something could be built into DirtPause to reset the effect parameters.

@telephon
Copy link
Contributor Author

One of my classic test cases is something like this

then you should be sending to two different orbits. The whole point of an orbit is to allow independent global effects and default parameters.

d1 $ s "bd" # delay 1 # delaytime 0.02 # delayfb 0.9 # orbit 0

d2 $ s "~ cp" # orbit 1

@telephon
Copy link
Contributor Author

This does lead to situations where if you change the first part to just d1 $ s "bd" # delay 1 then the delay unit will keep using delayfeedback 0.9 and you have no record of it, so that is weird.

Ok, that is indeed weird, we should probably change that default behaviour.

@telephon
Copy link
Contributor Author

telephon commented Feb 14, 2018

Not exactly, for example the room parameter was "global" (used within the reverb effect), which meant if you did d1 $ stack [s "bd" # room 1, s "cp" # room 0] the result was unpredictable.

I see. Yes, this is a real specification problem - I think it needs to be solved in tidal first (that is, we should discuss what we intend it to mean).

What you intend is probably something like this:

d1 $ stack [s "bd" # room 1 # orbit 0, s "cp" # room 0 # orbit 1]

But you see now why dirt is "dirty" - it isn't pure, has side effects. For global effects, they are desired.

@bgold-cosmos
Copy link
Contributor

Meant to post thoughts sooner...

then you should be sending to two different orbits. The whole point of an orbit is to allow independent global effects and default parameters

Right, the thing I was slow to realize is that "independent global effects" also includes not using any global effects on a "track". I.e. if I have some stuff that I don't want any effect on, and some stuff I do, I almost always need two orbits.

In practice what I've found is that I need to use a separate orbit for every global effect. But I don't always plan out my global effect usage ahead of time, so it means on-the-fly remembering that once I do decide to use one, I need to pull that stuff out and send it (and only it) to another orbit. And if I'm just using a separate orbit for each global effect it makes me wonder why I don't just build that into my definitions, so that room is always orbit 1, delay orbit 2, etc.

I've realized what I'm doing in this PR is almost equivalent to that, but instead of an automatic new orbit per effect I'm using a new bus per orbit. So in this PR if I want to have a d1,d2,d3 each with a different global effect I do it on a single orbit with multiple buses. Compared to the prior situation where I'd need three orbits with a single bus each.

The main functional difference in this PR, then, is just that I no longer have to worry about specifying orbits. Except for the rare case when, for example, I want something like two different reverb units playing on different sounds simultaneously. I personally find this easier to use, but I recognize that it's at least partly due to a particular mental model I keep in my head, and that any "global effect" implementation is going to have to stash the weirdness somewhere.

@yaxu
Copy link
Collaborator

yaxu commented Feb 19, 2018

@bgold-cosmos what do you think of the idea of the editor sending each pattern to a different orbit by default? (I mentioned it as part of this issue #74)

@bgold-cosmos
Copy link
Contributor

@yaxu I think I'd still prefer this PR, because of the following cases:

d1 $ s "bd cp cp cp" # delay "1 0 0 0" # delayt 0.02 # delayfb 0.8

Orbit-per-pattern would still chop off the delay tail, this PR "localizes" the send parameter (delay) so that it doesn't. I think the send localization is probably the main "real" change in this PR - messing with buses/orbits is behind-the-scenes stuff that could be made mostly invisible to the user either way.

d1 $ s "bd sn" # orbit "0 1" # room 1 # size "0.7 0.3"

Probably not a big deal, but orbit-per-pattern would force me to break this up into two patterns, or otherwise have to remember about whether I was stepping on d2's reverb or something. In general, automatic orbit-per-pattern means in the rare case where I do need/want to specify orbits, I have to remember to use something out of range of the patterns that I'm using.

@telephon
Copy link
Contributor Author

telephon commented Feb 19, 2018

Orbit-per-pattern would still chop off the delay tail, this PR "localizes" the send parameter (delay) so that it doesn't.

Yes, this is not good, I see. But this is really a missing separation of parameters between delay(send) and delay-wet-dry (or delay amp).

In principle it would be very easy to just give every global effect its own private bus. No need to add them to the orbit. I'd find that quite reasonable.

As you've mentioned, the disadvantage is that you can't play these effects into each other. Say you want to add a dub siren (which would have to be a global effect if it should be continuous), you sure want to add some delay to it …

But maybe there is a simple way we can think up to route between them. In the end it is mainly a specification problem on the tidal side (that is in our imagination while playing).

@telephon
Copy link
Contributor Author

So my main hesitation is right now: if we separated the send from the wet-dry for every effect, would there still be something missing?

If yes, we could just give every effect its own bus. If no, I'd prefer to fix that.

@bgold-cosmos
Copy link
Contributor

bgold-cosmos commented Feb 19, 2018

I'm not completely sure I understand, but I think what you're asking about is the distinction between what I might call "input gain" (send amount) and "output gain" (return amount) for each global effect.

Most of the effects are actually linear w.r.t. input gain, so there's no need for individual dry/wet knobs (return gain) on each effect. For example, if you want to double the output of the delay unit, you can just double the send (input) by doubling delay. In fact, all of the original effects are linear - one that isn't is the gated reverb I added, because it has a threshold parameter. So I added a "global" return gain for that one effect. There's then just a single dry parameter that controls how much of the original sound is mixed into the output. Here's a terrible attempt at a diagram:

image

Another point - even in the existing SuperDirt, the global effects all take input from the dryBus and output to the effectBus, so there's never been any signal routing from one global effect to another. The "local" effects do happen in a chain, and that's where the orderModules function comes in handy - but I don't think we've ever had much of a way to change this from Tidal itself.

@telephon telephon mentioned this pull request Mar 6, 2018
@telephon
Copy link
Contributor Author

telephon commented Mar 6, 2018

@bgold-cosmos @yaxu

I've spent some more time on thinking this through. Indeed, effect chaining was something I have removed already a while ago. So all effects are indeed independent already.

If I see this correctly, we have two separate issues, an easy one and a more difficult one:

  1. the delay effect has no delaysend parameter (only delayAmp). This I've already fixed in a branch.
  2. there is a specification problem what it means that you do not mention a certain parameter anymore in tidal.

Thus, say you first play:
d1 $ s "bd" # delay 1 # delaytime 0.1 # delayfb 0.99
and then change that to:
d1 $ s "bd" # delay 1 # delaytime 0.1
and then change that to:
d1 $ s "bd"

what should happen? When you don't mention it, should a parameter:
a. change to default
b. stay the same

Normally, I'd say b.. But the trade off is that your code doesn't represent the state of the system anymore. You may end up in a situation where you have a delay going and you don't know where it comes from or how to switch it off.

If you say a., then there are interferences like those you describe between parallel tidal streams.

One way to solve b. would be to have a tidal command that tells a certain effect to cease, or one that tells it to stick around.

Any thoughts?

P.S.

Essentially, these are two different semantics for code in general:

a. code means sound
b. code means communication with sound generating process

P.P.S.

The PR is #80.

@telephon
Copy link
Contributor Author

hi @bgold-cosmos ! I noticed you are still working on this. Are you not happy with the solution I have proposed (#80)? After your helpful comments I had realized that the extra busses are not necessary after all.

@bgold-cosmos
Copy link
Contributor

bgold-cosmos commented Apr 16, 2018

Actually didn't mean for that push to go here. I didn't want to switch branches right before AlgoSix, and haven't had a chance to thoroughly check out #80 since then. I'll definitely be doing that soon since I'd rather not have to deal with separate branches.

@telephon
Copy link
Contributor Author

No need to check out #80, it's already merged. You should have it in 1.0-dev. I am not saying it's complete, but ready to be tested and reconsidered what is still missing.

@bgold-cosmos
Copy link
Contributor

OK, I think it might be best to close here and continue the conversation in issue #75

@telephon telephon closed this May 25, 2018
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

Successfully merging this pull request may close these issues.

4 participants