function controlled audio rate looping breakpoint envelope
Could anyone offer me any advise on this? I have read the forum extensively and tried all the options I've found, but there are issues with all of them.
I need a breakpoint envelope that has the ability to create both linear and non-linear/exponential ramps, and will loop at audio rate. I’m using this to drive the wave~ object to manipulate the playback of audio files.
Here are the ways I've tried to achieve this and the issues I've run into.
curve~
There are a few problems with curve. Firstly, it doesn't play back linearly even when no curve is being applied. Secondly, the only way I know of to loop curve~, is to take the bang that occurs when the destination is reached and use that to re trigger the loop. Because you have to leave the signal domain to do this it causes issues if I want to loop something very short and fast. Thirdly, as it’s not phasor driven, I don’t know how I’d go about applying an amplitude window get rid of unwanted clicks.
mxjcurve
This obviously has the issues above apart from the fact that it will play back linearly when the curve value is set to 0.
function -> buffer (phasor driven)
As the above objects don’t give me the functionality I need. I moved on to where I am now, which is writing the output of function into a buffer, then using phasor -> wave~ (the envelope) -> wave~ (audio file). This gives me the linear playback and because of being phasor driven the ability to loop at audio rate, as well as being able to use the trapezoid~ object to apply an amplitude window if needed to get rid of clicks at the loop points. The problem with this is that wave~, play~ and groove~ interpolate between positions, so even though function is writing the correct shape into the buffer, if for example I jump from the end back to the start of the sample, instead of going immediately from 1 to 0, it very quickly plays back through the entire file to get there, so I get a little glitch. I’m assuming that wave~, play~ and groove~ all have to function like this as the interpolation is needed because the resolution being written by uzi -> function -> peek into the buffer is too low, so it's being interpolated to fill in the gaps.
I don’t really know what else to try though, so I thought I’d see if anyone else could shed any light on this.
What I ideally need is mxjcurve but with a choice of "one shot" or "loop" modes like rate~ and also with a loop sync output like groove~. I suppose if I can’t find a solution I’ll have to find someone to write me an external as I only have a rudimentary knowledge of c++ and java. It just seems that what I’m trying to do isn’t that complex, so it feels like it should be possible without resorting to that…
This is really strange. I've never had any problems getting a response on this forum before when I've had issues. I posted up several times about a month ago here too with no responses.
I'm not sure if this is because I'm missing something really simple so no one can be bothered, or that no one has an easy solution...
Even if the response is that it's not possible with standard Max objects that would be really useful, so I know that I need to go down the writing an external route.
Thanks guys
Hey
just to let you know we're out here! I haven't tackled this exact issue myself but I would agree that, as in your first 2 solutions, relying on both control rate and audio rate for this is a bad idea, so your third solution/concept is better - stay in the audio domain.
Now, I can't offer a solution, and I'm just free-forming here: a gen~ based phasor/ramp will take only one sample to reset to 0. I use [peek] inside gen~ (as part of a granulator) and there is no glitch when the phasor+peek reset. Think of [peek] as the equivalent of [play~].
Where you go from there to impose curves and windows is down to you I'm afraid. There are loads of people on here who will chip in and help. Also, try searching the forum again, using a variety of pertinent keywords - I'm convinced this question has been raised before, at least once - in the context of DIY loopers I think.
HTH
Brendan
I wonder if there is another solution where you have two wave~ objects, one with interp on and the other with it off. You then use a signal driven selector~ to switch between then only at the end of the cycle.
Or, if you can work out the direction of change (I assume you want to be able to go forwards as well as backwards) then you might be able to use rampsmooth~ on a non-interp wave~ as you can then choose the direction to smooth in.
These work in my head, but no promises they do in reality!
Thanks guys. I've only just seen these as I didn't have email alerts turned on...
I'll take a look later and let you know how I get on.
Brendan's suggestion re phasor and cross-fading between the two points is probably the route I'd go.
Here's a link to a gen~ patch I made a few years ago that outputs a series of phasors of arbitrary rates. (I would probably do all of this in codebox now, but it's an older gen~ patch of mine) It's probably not accurate to enough to use as an oscillator--I don't think I had figured out the whole subsample accuracy thing quite yet--but it should be pretty good. In addition to outputting the phasor signal, it also outputs the current index in the sequence, so you could definitely use this to look up the start, end, curve (etc.) values for each segment that are stored in a buffer~. It has a looping and a one shot mode, can be triggered at signal rate, and can also be scaled on the fly.
You'd need to convert the values from function, but that seems reasonable enough a task.
One other object to check out is zigzag~, though that's only for linear segments.
brendan are you typing this with a newsreader or something? it looks strange in a regular browser.
peek in gen~ can interpolate between two samples, is it possible to know more about @interp spline ?
maybe a way...
Forgot to put link in my earlier post. Here it https://cycling74.com/forums/pm-phasorseq/
Hi guys. Thanks for these suggestions. It will most likely be Thursday before I get a chance to look into them in any depth. I'll let you know how I get on. Thanks again.
Hey guys. Sorry it's taken me so long to get back to you. I've been tied up on other projects...
The most straight forward of the solutions above, requiring the least amount of reworking my patch, seemed to be Mark's idea of using rampsmooth~. I found a gen version of rampsmooth~ and put together a little patch that looks at the direction the signal is moving and smooths it, which works perfectly apart from very little clicks (much better that what I was getting with wave~ interpolation turned on) resulting from the fact the signal needs to be delayed by one sample to give ramp smooth time to switch direction. To deal with this I've added the history operator, which works perfectly when the signal is moving forward, but for some reason screws up the interpolation when the signal is moving backwards. It doesn't make sense to me why this is happening, but I'm pretty new to gen... Could anyone advise on what the issue is?
I've zipped the patch up with a kick sample, as it's easier to demonstrate the issue.
If you're just doing linear, then I'd strongly recommend using zigzag~.
If not, then you probably want something other than rampsmooth. Short on time now unfortunately, but if I get some time, will try to post something.
Unfortunately I looked into zigzag~ but being able to use exponential curves is a big part of what I'm working on...
Here it is again, but I've added the original wave~ with interpolation back into the patch so you can compare. They sound the same to me apart from the clicks. Even when slowed down to 1/10th of the original speed.
The gen patcher I've coloured yellow is the one I want to use as it totally gets rid of the clicks. I just can't understand why the history operator is interfering with the interpolation when the signal is moving backwards.
Peter, I've patched a version in gen using your idea of writing the function values to buffers which is working great. The only thing I'm not sure about is how the curve values that the curve object accepts differ from the curve values needed for the scale object. If I add 1 to them before passing to scale they're pretty similar with slight curves, but differ more the greater the curve.
Yes, that's the main difference. Curve~s curve parameter is iteratively calculated. I found the formula somewhere in the bowels of the forum once, IIRC. You could build a patch that produces an approximation of the coefficients. I would probably do it in JS, with it looped around the function object.
Alternatively, you might be able to get something happening with spline interpolation in gen~ by tweaking the tension.
hi ) I pusblished a gen~ version of this in May 2014 here: http://www.yofiel.com/software/cycling-74-patches/adsr-envelope
It's easily expandable to multistages, and you can change the slope by changing the pow factor. A pow of 1 would give you a linear line. There is a more comprehensive implementation with seven stages in codebox here: https://cycling74.com/forums/sharing-synthcore-gen-library
But you might find the code version a little daunting.
@Ernest, I think he's looking for a 1:1 mapping from function + curve~ into his code.
@ChristianCurtis, if I remember correctly, the EJies had (has?) a javascript envelope (ej.function?) that did curve~ segments. I bet you can find the formula in there and adapt it for audio.
Also, this formula from Christopher Dobrian looks promising as a way of calculating the exponential from the halfway point. You'd need to change the range so that instead of [0,1] it's [-1,1], but it might do.
Whatever the underlying function is, my guess is that it's fairly simple and probably not using an exponential calculation for each step (for efficiency purposes at the time it was written).
the links I shared *are* mathematically equivalent to function and curve.
@Ernest, As far as I can tell from the screenshots on your site--and please correct me if I'm wrong here--it looks like you're using these coefficients directly into pow to warp a constant time 0-1 ramp. The curve itself comes after the ramp, so if you change the exponent in the middle of the segment, this could create a discontinuity but would not affect the duration of the curve itself. In my tests, I have been unable to find a satisfactory, accurate way of calculating exponents with this that match the shapes produced by curve~. I suspect this is because curve~ is behaving more like a Bezier curve than an exponential weighting of a straight line. I'm not saying you can't produce curves with pow, but curve~ outputs a line when the third parameter is 0, rather than 1; a value of 1 will actually produce the steepest curve that curve~ can produce.
From what I have read, curve~ is using something different internally (some sort of iterative formula) where it is integrating a derivative(ish) of the curve; in that formulation, if you (theoretically, since you don't have this option for curve~) could change the curve factor midstream, the duration of the envelope would change, but there would be no discontinuity because you are operating on the velocity of the curve rather than on its position function.
UPDATE:
@ChristianCurtis In Help > Examples > gen, gen~.curve shows how curve~ works, and it is using an iterative formula. (The curv variable is altered at each sample over the course of the curve; there is a call to exp in the loop).
Ah, I did not know curve did that thanks for the information. What the docs say it does is that curve does a piecewise linear interpolation of an exponential function. So I erroneously assumed that is what it does. My mistake :)
What this design does is accumulate iincremental changes linearly, and produces a ramp by exponentiating those values. What you are asking for is that the changes in values after exponentiation are accumulated. It would then work the same as curve~, but as your friend observes, producing a linear value is very difficult from an arbitrary point, and one would really need a conditional test to check whether a linear response is requested and then switch to a different accumulator.
Thanks guys. I've only just had a chance to take a look at this. So essentially what your saying is that scale can't reproduce curves like the curve~ object, so if that's what I want I need to rethink that part of my patch? I'm not even fussed about the curves being exactly the same as the curve~ object, just so that they resemble what's drawn with function. I'll take a look at the links you suggested and let you know how I get on.
Correct. The curves are not the same, especially with higher values. You might be able to come up with a conversion that will give you the gist of things. A completely untested guess that might offer a starting point: 2^(a × curve^3) as the exponent for curve. For a, maybe try a value of 8 for starters. This formula won't be quite right, but the key thing is that it will be symmetrical; raising curve to an odd power weights the results towards 0 for positive and negative values. If you want an even exponent, just raise it to the power - 1 and then multiply by the curve parameter to preserve the sign.
While the above is good from a learning perspective, you'll get better results in less time if
you simply reuse the code box from gen~.curve to manage the ramping portion. The coefficients that curve calculates can be pre-calculated at control rate and stored into a buffer~ for greater efficiency.
This is probably no helpful in your effort to reproduce curve~, but what I do to redu ce clicks on preset changes in synthcore is this:
if (delta (henv1>0.2)) henv1 = smoother1(henv1);
this passes the envelope output through an integrator when there is a sudden jump in value. It solves the problem and allows use of standard envelope curves.