Help with tempo-guessing algorithm

I’m in a situation where I’m going to be measuring the length of some loops on the fly and deriving a ‘plausible’ tempo.

The simple algorithm I have in my head is that if the bpm works out at less than 80, double it: if greater than 160, half it. And, that would ideally be recursive, so if it is less than 40, multiply it by four, if greater than 320 divide by four, and so on.

I have the function below that works, but seems clunky and very un-functional with all those case statements. Also, I’m not properly dealing with values > 1280 or < 10.

(
~h = {|x| case
	{x < 20} {x*8}
	{x < 40} {x*4}
	{x < 80} {x*2}
	{x > 640} {x/8}
	{x > 320} {x/4}
	{x > 160} {x/2}
	{x}
});

~h.(79) // too slow, double it
// etc

I’d love to see a more elegant solution? It feels to me like there is something modulo-ish going on here, but I can’t figure it out!

You could use recursion. This would help you work with very small or very large numbers, although I’m sure a programmer could find a more efficient way.

(
~h = {|x| case
    {x < 80} {~h.(x*2)}
    {x > 160} {~h.(x/2)}
	{x}
}
)

// tests
~h.(39);
~h.(79);
~h.(101);
~h.(165);
~h.(340);
~h.(85 * 2.pow(10));
~h.(86.7 * 2.pow(-10));
1 Like

Ah! The recursion looks so simple once you see it, thanks.

Still… I’m convinced there must be a clever one-liner that would do it :slight_smile:

~h = {|x| x * 2.pow(log2(80 / x).ceil)};

Wow, I owe you a beer! And, I’ll buy myself one if I can actually figure out how this works…

That’s why I generally try to avoid clever one-liners :slightly_smiling_face: They tend to be unreadable, but here is hopefully a simpler multi-line breakdown with annotations:

(
~h = {|input_tempo|
    var min_tempo, tempo_ratio, log2_ratio, number_of_doublings, mult, result;
    min_tempo = 80;
    [input_tempo, min_tempo].debug("input, target tempo");
    tempo_ratio = min_tempo / input_tempo;
    tempo_ratio.debug("tempo ratio");
    log2_ratio = log2(tempo_ratio);
    log2_ratio.debug("log2 ratio");
    // log2_ratio is the number of doublings
    // of input_tempo required to obtain min_tempo.
    // A negative number results in halvings, not doublings.
    // This is a float, but you want an integer number of
    // doublings/halvings, and you want the result >= min_tempo,
    // so I use the ceil method to round up to the next integer.
    number_of_doublings = log2_ratio.ceil;
    number_of_doublings.debug("number of doublings required");
    // perform the doublings (or halvings):
    mult = 2.pow(number_of_doublings);
    mult.debug("so multiply input by");
    result = input_tempo * mult;
    "% * % = %".format(input_tempo, mult, result).postln;
    result;
};
)
2 Likes

Very, interesting thanks!

@PitchTrebler - I love this!:

~h = {|x| x * 2.pow(log2(80 / x).ceil)};

How would one go about defining an upper limit which is not necessarily the double of the lower limit? Say, lo limit of 60 bpm, hi limit 132 bpm so ~h.(246) would yield 123 and not 61.5?

It has been way too many years since logarithms class, so I can’t figure this one out. I thought this might work…

~h = {|x, lo=60, hi=132| x * 2.pow((log(lo / x) / log(hi / lo)).ceil)}

…but it produces incorrect results on very small or large inputs, so don’t use that. I would just go with the recursive function from my first reply.

1 Like