ADSR with per-stage ring mod?

Inspired by the XAOC devices Zadar wavetable ADSR, I’ve been looking into how to make more complex envelopes. Using a wavefolder on an ADSR is a good start, but I’d like to be able to do something different per stage. One way to do this would be to provide the modulation sources I want into a sequential switch and then have each stage transition of the ADSR go so the next stage with the switch, having the output of the switch and output of the ADSR be multiplied (ring mod). The issue with this is both that it’s a bit complex to wire up and that it has issues on switch, that is it doesn’t wait for a zerocrossing or the two modulating waveforms to be equal to do a switch, so it can cause the envelope to have awkward transitions. What I’d really like is an ADSR module that has different ring mod inputs, one per stage, built in that does this waiting for a zero crossing or modulation equality internally, but even with the current modules I’m struggling to put something together that’s not totally awful to use. Instruo’s cèis ADSR helps with this as it has stage transition triggers/gates, but I’m still struggling.

I’d love to see some patches based on this idea and I’d welcome any devs to make this module, even if it’s paid.

2 Likes

I’m working on a set of envelope generators and will gladly experiment with this. Sounds like a cool idea.

2 Likes

I didn’t really understand much of that but it sounds like the sort of thing that you could do with DHE modules? Maybe the Stage modules in particular?

if you use the complexDAHD you have a trigger at end of every status (EOD EOA EOH EOC) and you can use them to ON - OFF processing of the Env outputs

1 Like

you can sequence the complexDAHD (they have an input for SUM) or you can use a sequence of BZ-ENVELOPE one EOC triggering the successive: any number of ENV you want, add their outputs and you get supercomplex ENV

1 Like

That’s something i never tried or even imagined ! Thanks for the suggestion :slight_smile:

1 Like

Awesome. I’m crap at DSP which is why I haven’t done it myself, but if you put up beta builds or it’s open source I’ll happily try to give as much technical debug info as possible.

I originally made a feature request over on the BogAudio repo, which you can look at here: Module Request: Sample Based ADSR · Issue #156 · bogaudio/BogaudioModules · GitHub

you’ll see in the issue I was originally thinking instead of doing the ring mod thing as dedicated inputs, doing ring mod with pre-defined samples, but more ‘modular’ thinking led me to the conclusion that I could always just load in sample-player modules to do the same thing and that this is much more flexible.

Also, sorry for the crappy explanation in the original post

+------------------------------------------------------------------------+
|                                                                        |
|                                                                        |  +------------------------------------+
|                                                                        |  | 4, independent modulation sources  |
|                          XX                                            |  |    One per stage                   |
|                         XXXX                                           |  +--+--+----+----+--------------------+
|                        X  + X                                          |     |  |    |    |
|                       X   |  X X                                       |     |  |    |    |        +---------------------------+
|                    XX     |     X                                      |     |  |    |    |        |                           |
|                   X       |     XX                                     |     |  |    |    |        | 4:1 Sequential Switch     |
|                  XX       |      XXXXXXXXXXXXXXXXXXXXXXX               |     |  |    |    +--------+                           |
|                XX         |       +                   + XXX            |     |  |    |             | I  Needs to wait to switch|
|              XXX          |       |                   |   XX           |     |  |    |             | N  until the modulation   |
|             XX            |       |                   |     XX         |     |  |    +-------------+ P  sources are equal      |
|           XX              |       |                   |       XX       |     |  |                  | U  otherwise there.s a    |
|           X               |       |                   |         XXX    |     |  +------------------+ T  sharp transition.      |
|         XX                |       |                   |            XX+ |     |                     | S                         |
|       XXX                 |       |                   |              | |     +---------------------+    ADSR needs to hold     |
+-----+------------------------------------------------------------------+                           |    current level if       |  Note, this has a side
      |                     |       |                   |              |                             |    waiting on the mod     |  effect of effectively
      |                     v       v                   v              v                             |    sources to align       |  making it an AHDHSHR,
      |                    EOA     EOD                 EOS            EOR                            |                           |  holds at each point until
      |                                                                                              |                           |  the modulation allows for
      |                     +-------------SUM-TRIGGERS-----TRIG-ON-EACH-STAGE----------------------------->Next Input            |  a transition.
      |                                                                                              |                           |
      |                                                                                              |                           |
      |                                                                                              |                           |
      |                                                                                              |                           |
      |                                                                                              +----+----------------------+
      |                                                                                                   |
      |                                                                                                   |
      |                                                                                                   |
      |                                                                                                   |                            +-------------+
      |                                                                                                   +--------------------------->+             |
      |                                                                                                                                |  RINGMOD    |
      |                                                                                                                                |             |
      |                                                                                                                                |  *~         |
      +------------------------------------------------------------------------------------------------------------------------------->+             |
                                                                                                                                       +-------------+

I’m not sure if this explanation helps at all, but it’s the best I can do at the moment. Essentially, doing this all in one module is what I’m looking for.

1 Like

poke you ever try anything out with this idea? I was considering trying to make something with vult and VCV Prototype provided I actually get some free time this semester

No, sadly I’ve been wrapped up with a fair bit of schoolwork and haven’t had time yet.

@Vega, did you give this any more thought/tinkering? It seemed like a really interesting idea!

edit: If I understand the design, the goal is to change the different stages of the envelope’s output in arbitrary ways (so, starting simple, the A stage might have a slow wobble as it’s going up, the D stage might have a fast wobble as it’s going down, etc.) The stages themselves will have time constants set, but the module should be able to lengthen (never shorten) the time constant until the final modulated value of one stage equals (or is within some acceptably small delta from) the starting modulated value of the next stage, so that the envelope doesn’t suddenly jump.

If that’s right, there are two (I think solvable) design challenges that might come up:

  1. What happens if a modulation source comes in that keeps the output of one stage permanently out of range of the other stage? Say the A stage is going from 0 to +4V and it’s fed a constant (i.e. DC) output of 1.25, and the D stage is expecting to start at +4V since that’s where the A stage ends. The A stage ramps up from 0 to +5V and the D stage never starts. (I’m not saying this is a correct use of the plugin, and the answer might just be “don’t do this”, but is it something the design should handle?)
  2. What if two modulation sources are unsync’d (or sync’d) in a way that keeps the output of one stage away from the input of the other stage? Imagine the S stage and the R stage are both being modulated by sine waves that are the same frequency but out of phase. S finishes its time constant and is waiting to go to R while wobbling up and down, but whenever S is being modulated down, R is being modulated up, etc. and they never get close enough for S to advance to R. This may be a harder issue since it seems more likely to occur with ordinary use–even if this example is contrived to never advance, some stages might go a lot longer than the user would expect.

The basic issue (if I understand the proposal correctly) is that if the modulation comes in from outside (which I agree is the right “modular” approach for something like this), this module has no control over when they start or how they act.

What it could do is fade in the modulation of the next stage (using some other time constant, maybe adjustable) so that the transition function was chasing a fixed, rather than a moving, target. This should solve #2.

Regarding #1, there could also be a maximum hold for each stage (which would solve the freeze problem), though that would still jump. But internal slew might help with that…

That description was spot on.

For both points, I think your recommendations are good. This would probably be a context menu thing, where the options could be ‘wait as long as necessary for a valid transition value’ or ‘forcibly make a slew’d transition after n seconds’. There could also be an extra trigger input that if brought high when ‘stuck’ would trigger a slew to the next stage, this could either be attached to clock to fix stuck stages or be abused to add a sustain stage at any point.

1 Like

Cool! And I like those ideas (incl. extra input). I think of context menus as the equivalent of trimpots/DIP switches inside modules (just less annoying than with hardware!), and that seems like a sensible use of them.

I’m working on a big(-ish) interface module right now (announcement soon) but have been itching to do some more DSP-y things after that and might be able to pitch in on this. Alright if I try to work up a prototype?

If you want to try out a prototype that’d be 110% cool with me, just if you’re gonna sell the finished work I ask that you toss me a free copy :wink:

1 Like

Sweet! I think this could be an excellent free module and will share the prototype with you first when (hopefully not if) I have something worked up, then we can discuss open development/release options and hopefully get something out into the world!

Did some thinking, and I think there’s a fundamental question about the operation we’ve failed to consider yet.

Say you put a sine as the modulator on the attack stage, does that sine get added directly or get multiplied with that stage?

image

That is, should the amplitude of the modulator be controlled by the amplitude of that stage (green) or should the modulator be added directly (blue)

Something else to consider is what other outputs would be useful? Having the raw ADSR out is obvious, but I think it may make sense to have beginning/end of stage outputs too.

Another thing, should the ADR times be able to be modulated, or does that add too much complexity? What about the ADR slopes (lin/log/exp)?

Obviously, there’s a point of balance between features, usability, and ability to be programmed. I’m not interested in something as complex as shape master for this, and I think it’d be easy to accidently end up making something like the nysthi bezier module instead of the actual goal.

All great questions, and I agree that “oops, we made a bezier” is a potential pitfall!

Off the cuff thoughts:

As to + vs *, probably switchable per stage (maybe even crossfadeable, if that proves interesting?) Some sort of bias control would also be useful for *, since literal multiplication by an unbiased bipolar signal is going to drop the envelope to zero every time there’s a zero-crossing. Half- or full-wave rectification could also come in handy. Since the multi-stage modulation (and smooth transitions thereof) is the quiddity of the plugin, I think lots of options to massage modulators and play well with various inputs is a good thing.

Outputs are so easy to code up and so useful that I think we shouldn’t stint on them. If we want to save HP we can always put EOS triggers, unmodulated outs, etc. on an output expander (pretty common place for them).

In terms of ADR time and S level, maybe along the lines of Fundamental VCA, knobs and CV only? The complexity here is the modulation, and a simpler envelope model might help bring that out.

I think the underlying ADSR should probably start simple, by which I guess I mean linear. My mind’s ear isn’t finding cases where modulation with exp or log would be fundamentally more interesting than modulation with lin (and arguably since exp or log spend more time near extrema than lin does, they’re closer to just applying the modulated signal by itself). Besides, a fancier version or tacked-on options can always come later.

I’ll add a question: I’m assuming we want this to be polyphonic in terms of input gates to output envelopes. If the modulators are monophonic, the ups and downs would be sync’d across all voices, so they would be wobbling in unison for a given stage (I’m imagining the simple case of an ADSR driving a VCA here).

Poly modulation inputs would be trivial to do, and we would of course apply a single-channel mod to all channels of the envelope if that’s all we got.

But getting really wild poly results with unsync’d modulators would require feeding a whole bunch of inputs in a useful configuration. We should assess whether existing poly LFOs are suitable for interesting results.

I can imagine trickery with delays (or a weird application of allpass filters, kind of like the “dome filter” in Bode shifters) to turn one LFO into a bunch for a poly context.

I can also imagine a companion LFO module designed to produce poly LFOs with useful characteristics for this ADSR!

This is turning into an open development thread, so anyone else with thoughts or requests should pile on…

1 Like

re: + vs *, yeah, that sonuds like the right way to go. I’m not sure about crossfade, but having it be per-stage sounds like a good thing, as does rectification. Rectification is cheap anyway, so that shouldn’t be a problem.

I think you’re right, expanders for EOS++ is the way to go. That would let us make something simple then add on as necessary- even the lin/log/exp could be done via expander if it does prove interesting.

As for poly, while your ideas are good - especially pointing out the sync’d modulation - I don’t know if we’d want to deal with the complexity of it at first. It might be easier to just make a mono prototype and then add poly after the fact, that way we can actually optimize for performance in one shot instead of rethinking everything about poly with each change.

I think if we added poly I’d want it to have ADR time and S level control inputs though, that way different envelopes could actually have different times. This would be particularly interesting for feedback-ish patching where one shorter envelope is used as the modulator on a longer envelope’s stage.

Going back to the + vs *, should the envelope’s output be able to drop below 0v? I don’t see any reason it shouldn’t? I’d like to provide an inverted output too, so that would then be able to go above 0v

Had to take a leave of ADSR absence to get this out but pondering your points now and might start coding this weekend :slight_smile:

Edit:

  • Agree on mono prototype, let’s iron things out in the simpler case, make sure it’s interesting enough to proceed (pretty sure it will be!), and then add those options…
  • Interesting poly patching case, definitely
  • Inverted output sounds good. I think some downstream stuff might act surprising if envelopes go below 0V, so I wonder if an output rectification option might be a good idea too, as that would play well with modulators as the envelope reaches its lowest level (and the UI and block diagram has rectifiers in it for inputs anyway, so we’re not adding a new concept).
1 Like

Sounds good.

Thinking about checking the ‘equality’ for moving to the next stage, I don’t actually know what bit-depth things in rack are internally? From work I did earlier this semester with steganography I know with 24 bit audio the 8 least significant bits can all be changed with basically no audible artifacts, so maybe we would just check if the first two bytes are equal? If we assume we can do at least a tiny bit of interpolation (and that it doesn’t kill performance) I imagine we could get away with a lot more variance

1 Like

Excellent!

Rack runs on 32-bit floats internally; the typical approach is to establish an epsilon value and treat them as equal if the abs() of their difference is <= epsilon. There are other issues but since typical values in Rack are by convention reasonably well distributed over 0±10 I think we’ll probably be OK with a simple approach. (Any approach will be super-cheap computationally).

Suggest we start with epsilon settable from the right-click menu for development and if we find a value that seems objectively robust in application (i.e. it avoids audible glitches/unpleasant slew swoops but is willing to move from stage to stage) we probably just hard-code it (it’s not the kind of parameter you’d typically expect a user to tweak). I can also imagine a ramping approach where the epsilon automatically broadens if the inter-stage waiting period has gone on long enough.

1 Like