Hi Liam
A thread that covers the same topic: http://lists.puredata.info/pipermail/pd-list/2016-01/112812.html
On Thu, 2016-02-11 at 05:14 +0000, Liam Goodacre wrote:
Any DAW or sequencer has a global setting which allows you to change the tempo smoothly across all elements. But fool around on PD for a while and you will realize that this is not a simple operation. If you have more than one time object going at once, it is very hard to keep them in time with each other while changing the period.
Consider the example of two metros set to 500 and 1000 ms, banging in synch with one another. The "cold" inlet of both are connected to a float, with the second one being multiplied by 2 along the way. I'm sure you don't even need to try this to know that the two metros will quickly faze out quite if you scroll through the value. The metro duration updates only after the cycle is complete, so one updates before the other and it quickly gets chaotic, especially if you drag the value for more than one complete metro cycle. Of course you could keep them in time by using a trigger to reset the metros, but this is not good from a musical perspective, because I don't want to go restart the bar every time I change the tempo. Another possibility is to store the new time value and load it only when both cycles coincide. This should keep both metros in time, although the transition wouldn't be very smooth.
Checkout the help of [delay] (>= Pd 0.45). It illustrates a new 'tempo' method that tackles this exact problem, i.e. using 'tempo' allows to change time intervals in the middle of a running interval. The same method can be used for [metro] and [timer], too.
[line] works differently, in that the time is specified in the "hot" inlet. But this also doesn't update smoothly if you interrupt it with a new message before the cycle has finished, because the new value sent to it will take the last point as a starting point. The total duration of its cycle will be {elapsed time} + {new time}, which isn't very good if you want to change tempo smoothly. I worked on trying to solve this for a while, and came up with a prototype solution which I am attaching. It works by taking the new time and subtracting it from elapsed time for the new value, so that the line finishes its cycle as if the new time value had been given at the beginning. The patch works reasonably well for one iteration, but completely fails for more than one (ie. if you scroll through the time values). I still haven't figured out why.
I think you're on the right path. If an update happens, measure the time since the start of the ramp, calculate the current position, generate a new ramp that starts at the current position and takes the proportional rest of the time interval to complete. I cannot think of another way to achieve that.
I think this is supposed to work even when updating more than once during one ramp.
Before I open up for suggestions, here are a few other points:
- The simplest solution to this would be to have one and only one
time object, set to say 1/16th beat or whatever is the smallest time interval you want, acting globally on all other sequenced events. This would work simply and elegantly, but unfortunately it is not an option for my project. Keeping time locally is essential to me, and I would sooner give up tempo change altogether than give this up.
And it doesn't work well, if you want to have many different ratios of beats in parallel. Soon your common partial interval will be so small, that [metro] significantly hogs the CPU. If you're interested in keeping many different timing ratios in sync, checkout master.pd and abs/master-poly.pd from https://github.com/reduzent/netpd-instruments/ . While [master] provides the master clock, any instance of [master-poly] can derive any arbitrary integer ratio time interval for it (for instance, [master-poly 9 5] would create 9 ticks withing the time of 5 master ticks).
- Another option would be to use the [timer] object, either as an
alternative to the others, or to help calculate feedback for them. I'm hesitant to do this because a: it seems like it would require more CPU and b: it it seems like an empirical solution to a deterministic problem.
I don't know how this is a deterministic problem. You don't know when the user decides to make a tempo change. So you're left with measuring the time elapsed since ramp start or last interval or whatever. If you know beforehand, when a tempo change is going to occur, then you don't need [timer] to get elapsed time.
My intuition says that this problem can be solved mathematically, without resorting to measurement. However, I am willing to be convinced that this is the right way to go.
- I assume that [line] and [line~] will work identically in this
regard,
No, [line~] does start and end ramps exactly on block boundaries, while [vline~] is sub-sample precise. Only [vline~] takes into account the precision of [metro] and [delay]. For the things you're working on, I strongly suggest using [vline~], since [line~] would cause all kinds of glitches.
and that [delay] and [metro] will work similarly. But [line] and [delay] work in very different ways, and so the solution, if it exists, is likely to be quite different for these objects. Ultimately I want to find a solution for all time objects, so I'm willing to hear anything you've got!
Checkout the 'tempo' method of [metro], [delay], [timer].
Roman