On Mon, Nov 13, 2017 at 12:25 PM, Alex McLean <alex@slab.org> wrote: > I don't know if this is relevant, but in tidalcycles (tidalcycles.org) > I dealt with the problem of how to sample a function of (rational) > time by instead…

On Mon, Nov 13, 2017 at 12:25 PM, Alex McLean <email obscured>> wrote: > I don't know if this is relevant, but in tidalcycles (tidalcycles.org) > I dealt with the problem of how to sample a function of (rational) > time by instead working with functions of timespans, basically (Time, > Time) -> [(Time, Time, a)], i.e. a function of start and end times, > which returns a list of values and the timespans which they are active > within (which will of course intersect with the input timespan). That sounds kind of like the hybrid function + explicit breakpoints approach I was theorizing about, except the other way around. I was thinking it would be ([(Time, Time)], Time -> a), or perhaps [(Time, Time, Time -> a)], where the Time ranges are marking different kinds of functions. But, if I understand what you're saying, you have a function that returns time segments, e.g. f (start, end) = [(0, 1, a), (1, 2, b)]... or something? I assume the value 'a' is constant within its time range, which makes it like the piecewise-constant version? In that case, why have a separate end time, instead of just the start times? What types are the values? If they're numeric, how do you handle interpolation between them?

I don't know if this is relevant, but in tidalcycles (tidalcycles.org) I dealt with the problem of how to sample a function of (rational) time by instead working with functions of timespans, basically (Time, Time) -> [(Time, Time, a)], i.e. a function…

I don't know if this is relevant, but in tidalcycles (tidalcycles.org) I dealt with the problem of how to sample a function of (rational) time by instead working with functions of timespans, basically (Time, Time) -> [(Time, Time, a)], i.e. a function of start and end times, which returns a list of values and the timespans which they are active within (which will of course intersect with the input timespan). This can be used to represent functions of either continuous or discrete time. In the former case, you can just take the midpoint of the given input timespan to compute the output from. So the duration of the input timespan is a bit like the resolution of the sampling rate. With both continuous and discrete functions of time represented in the same type, it becomes very easy to compose them together.

On Sun, 12 Nov 2017, Evan Laforge wrote: > I see, I guess you mean NodeList.lookup is looking in a binary tree. right > Right, I'm not worried about approximation, as long as it's not too > approximate. It's just that I'm…

On Sun, 12 Nov 2017, Evan Laforge wrote: > I see, I guess you mean NodeList.lookup is looking in a binary tree. right > Right, I'm not worried about approximation, as long as it's not too > approximate. It's just that I'm not sure about the complexity of the > implementation for those operations. An approach could be: Implement (zipWith f xs ys) in some way and then use curve fitting in order to construct approximate cubic functions. An exact implementation would have to be implemented for every operation, and might be feasible only for basic operations like multiplication. A general solution might sample the curves. The Fit example shows how curve fitting works. Alternatively you may warrant the correct function application only at the nodes and perform some differentation rules there. E.g. if you have node (ya,dya) from the first function and (yb,dyb) from the second function you may set the node of the multiplied curve using the Product rule to: y = ya*yb dy = ya*dyb + dya*yb > Also I see I had to do some awkward binary search guessing to find the > value at a specific 'x'. I forget the details now but I guess since > the bezier's output is (x, y) pairs, I can't just directly find the y > for a given 'x', hence the searching. That's probably pretty > inefficient and a sign I should have used a better technique. It > looks like Piece.hermite1 takes an arbitrary x and directly computes > the result, so it doesn't have that problem. Searching should be only necessary when you compute values of a function that is a concatenation of some basic pieces. Piece.hermite1 interpolates within one piece, thus no search. The same would apply to Bezier curves. > What's the difference with the other Types, like cubicLinear and > cubicParabola, given that interpolatePiece functions are all the same? > I gather the Basis.coefficientsToCubicLinear and CubicParabola are just > using different techniques and inputs to compute hermite1 values, which > ultimately goes into the same kind of interpolation. Right, they all are different ways to determine the cubic polynomials. Once you have the cubic polynomials, their interpolation is always the same. > I guess the idea with the Hermite spline is that since in and out > slope are the same, you automatically get a smooth function. right > But on the other hand, how do you influence how sharp the curve is? > With the bezier implementation I can get back to linear interpolation by > setting both weights to 0, or get a sigmoid kind of shape by setting > them both to 0.5. Sigmoid type would be slopes=0 for Hermite interpolation, Linear would be slopes=(y1-y0)/(x1-x0). > However, the Wikipedia page claims that Hermite and Bezier are simply > different representations for the same thing, and can be converted > mechanically, so surely they should have the same capabilities? Right, if of the same order they span the same set of functions, here: cubic polynomials. More generally: n degrees of freedom require polynomials of order (n-1).

On Sun, Nov 12, 2017 at 3:39 PM, Henning Thielemann <lemming@henning-thielemann.de> wrote: > > On Sun, 12 Nov 2017, Evan Laforge wrote: > >> Oh ok, I'll take a closer look at the source. I thought the >> underlying representation was a…

On Sun, Nov 12, 2017 at 3:39 PM, Henning Thielemann <email obscured>> wrote: > > On Sun, 12 Nov 2017, Evan Laforge wrote: > >> Oh ok, I'll take a closer look at the source. I thought the >> underlying representation was a list, but it's hard to tell how >> anything works with no documentation. > > Please look at the two examples. I see, I guess you mean NodeList.lookup is looking in a binary tree. > Multiplication cannot be done exactly. You could try an approximation. > > For max(f,g) you need to solve the cubic equation f(x)-g(x)=0 and split at > the zeros of f-g. > > For ad-hoc functions you need an approximation, again. But ... audio rate > sampling is approximation, too, isn't it? Right, I'm not worried about approximation, as long as it's not too approximate. It's just that I'm not sure about the complexity of the implementation for those operations. If it's too hard for me to figure out, then it's as good as impossible! If it's possible, but makes me have to think really hard for a long time every time I want to add some new operation, or worse wind up with something which is not obviously correct and hence buggy, then that's not too great either. I tend to experiment with possibly "unprincipled" ones which are nevertheless useful in practice, for instance I quite frequently use a kind of scaling where (-1, 0) scales from 0 to x, and (0, 1) scales from x to the max, which is usually 1, and a nice thing about the constant samples is you just give the function to a 'zipWithY :: (Y -> Y -> Y) -> Signal -> Signal -> Signal' and you're done. Anyway, there's nothing for it but to try and see how it looks like it'll turn out. Thanks for the reference to cubic hermite splines. I previously used cubic bezier curves, and muddled through the implementation from various wikipedia pages. It takes two weights, which influence flatness at the beginning and end, which is ok to use, but less general than a slope. Also I see I had to do some awkward binary search guessing to find the value at a specific 'x'. I forget the details now but I guess since the bezier's output is (x, y) pairs, I can't just directly find the y for a given 'x', hence the searching. That's probably pretty inefficient and a sign I should have used a better technique. It looks like Piece.hermite1 takes an arbitrary x and directly computes the result, so it doesn't have that problem. What's the difference with the other Types, like cubicLinear and cubicParabola, given that interpolatePiece functions are all the same? I gather the Basis.coefficientsToCubicLinear and CubicParabola are just using different techniques and inputs to compute hermite1 values, which ultimately goes into the same kind of interpolation. I guess the idea with the Hermite spline is that since in and out slope are the same, you automatically get a smooth function. But on the other hand, how do you influence how sharp the curve is? With the bezier implementation I can get back to linear interpolation by setting both weights to 0, or get a sigmoid kind of shape by setting them both to 0.5. However, the Wikipedia page claims that Hermite and Bezier are simply different representations for the same thing, and can be converted mechanically, so surely they should have the same capabilities?

On Sun, 12 Nov 2017, Evan Laforge wrote: > Oh ok, I'll take a closer look at the source. I thought the > underlying representation was a list, but it's hard to tell how > anything works with no documentation. Please look…

On Sun, 12 Nov 2017, Evan Laforge wrote: > Oh ok, I'll take a closer look at the source. I thought the > underlying representation was a list, but it's hard to tell how > anything works with no documentation. Please look at the two examples. > It seems like arbitrary math operations might be tricky though. All > you need to resample the signals to have the same breakpoints is a > split for the curve, but what about multiplication, or max, or some > more ad-hoc function? Multiplication cannot be done exactly. You could try an approximation. For max(f,g) you need to solve the cubic equation f(x)-g(x)=0 and split at the zeros of f-g. For ad-hoc functions you need an approximation, again. But ... audio rate sampling is approximation, too, isn't it?

On Sun, Nov 12, 2017 at 9:44 AM, Henning Thielemann <lemming@henning-thielemann.de> wrote: > It does the binary search for you. Summing two curves and doing the merge > would be a nice addition, though. Oh ok, I'll take a closer look at…

On Sun, Nov 12, 2017 at 9:44 AM, Henning Thielemann <email obscured>> wrote: > It does the binary search for you. Summing two curves and doing the merge > would be a nice addition, though. Oh ok, I'll take a closer look at the source. I thought the underlying representation was a list, but it's hard to tell how anything works with no documentation. It seems like arbitrary math operations might be tricky though. All you need to resample the signals to have the same breakpoints is a split for the curve, but what about multiplication, or max, or some more ad-hoc function? I guess constant samples work with anything, linear ones only work for linear functions (so multiplication yes, but something like max or an ad-hoc function requires an ad-hoc solution), and curves... maybe you wind up having to just sample them. But that means that a fancy curve representation could degenerate to flat samples as soon as the "wrong kind" of transformation is applied, which in practice might mean just about always. Of course linear has the same kind of problem, but maybe it's easier to come up with the ad-hoc solution (e.g. for max, find where they cross and splice there). For instance, long ago I actually originally used the linear segments representation, but even that gets hard for integration, while for flat samples it's trivial. Now that I think of it, I think that's one of the reasons I gave up on linear segments way back when. I guess you can find where curves cross in exactly the same way as lines, but somehow it seems like it might be more complicated. I'll try sketching out all of the operations I'll need.