Messing about with this simple patch to calculate BPM change when something is slowed down two semitones and I wondered - how would I find the most whole numbered/least amount of decimal places change/ratio?

I think this is probably a basic maths question, but I have no clue how to approach it. Thanks!

]]>Messing about with this simple patch to calculate BPM change when something is slowed down two semitones and I wondered - how would I find the most whole numbered/least amount of decimal places change/ratio?

I think this is probably a basic maths question, but I have no clue how to approach it. Thanks!

]]>David. ]]>

So if you lower the pitch by 2 semitones you would divide the tempo by 2 to the power of 2/12

The tempo ratio (old to new) would be 1 : (1 / 2 to the power of 0.166666667)

The tempo ratio (new to old) would be (1 / 2^0.166666667) : 1

As sure as I can be...

You can do the math in your patch using [pow]

David.

I suppose I’m looking for the most ‘harmonic’ ratios. Would be useful going forward too I suppose when working with anharmonic spectral stuff.

]]>]]>

Sorry, I didn't see your patch (I always miss them when they are at the top of the post).

Try this........ bpm_ratio.pd

The power needs to stay positive and then is multiplied for "up" and divided for "down".

The ratios you get will keep you in tune while trying to get integers will not (except for octaves of course)..

If you want to detune then remove [int].

Screenshot below is a simpler patch but very slightly less accurate... taken from G09.pitchshift in the Pd examples.....

David.

So good ones would be:

69 BPM gives 61.472

59 BPM gives 52.563

A bad one would be:

77 BPM gives 68.5992

It's for a studio project where I have to pitch and slow everything down 2 semitones. The starting point isn't so important, but the nearer to a whole number it is the better precision there is with keeping everything on the grid.

So, starting with the smallest number of decimal places and ending with the smallest number of decimal places is preferable, does that make sense?

So, starting with the smallest number of decimal places and ending with the smallest number of decimal places is preferable, does that make sense?

Formally, it doesn't make sense.

The ratio for 2 semitones down is `2 ** (-2 / 12)`

. 2 is a prime number. Raising any (positive) prime number to a fractional power results in an irrational number, with infinitely many decimal places (without ending up in a repeating sequence).

A rational number times an irrational number must be irrational. So your initial bpm value * the ratio is irrational and has infinitely many decimal places. To say "this one has 3 places" glosses over the real situation.

What you're really doing is rounding this irrational number to an arbitrary number of places. The denominator of the rounded number will be `10 ** num_places`

-- thus both the numerator and denominator are integers and the result is rational.

The difference (or quotient, depending how you want to measure it) between `bpm * ratio`

and the rounded version is an *error* value.

And the math problem, then, is to minimize the error.

You can see it more clearly if you use a language with double precision floats, e.g., SuperCollider:

```
f = { |bpm, semitones = -2, places = 3|
var r = 2 ** (semitones / 12); // '.midiratio'
var bpm2 = bpm * r;
var rounded = bpm2.round(10 ** places.neg);
// for easier comparison I'll "absolute-value" the error values
var errorDiff = bpm2 absdif: rounded;
var errorRatio = bpm2 / rounded;
if(errorRatio < 1) { errorRatio = 1 / errorRatio };
[bpm2, errorDiff, errorRatio]
};
f.value(69); // [ 61.472011551683, 1.1551683407163e-05, 1.0000001879178 ]
f.value(59); // [ 52.56302437028, 2.4370280016228e-05, 1.0000004636394 ]
f.value(77); // [ 68.599201296806, 0.00020129680612513, 1.0000029343985 ]
```

... where, indeed, the error for 77 * r is about an order of magnitude worse.

@jameslo -- "It would be cool if you were asking https://math.stackexchange.com/questions/2438510/can-i-find-the-closest-rational-to-any-given-real-if-i-assume-that-the-denomina "

~~I think this is exactly what the problem reduces to -- what is the closest rational to ~~`x`

where the denominator = 1000. (However, the numerical methods in the thread will likely evaluate better with double precision floats. Pd prints 6 digits so it may not be clear what you're seeing.)

Actually something else... if x is the original bpm and y is the adjusted, find x and y where the error is minimized. So far the assumption is that x is an integer, but maybe the error could be even lower if they're both fractions.

hjh

]]>@ddw_music Thank you for taking the time to write such a detailed response, I *think* I finally understand.

So what I actually mean is I want to minimise the error in a BPM change of two semitones, and find the start and end values where it's lowest, even if they're both fractions. I think?

]]>So what I actually mean is I want to minimise the error in a BPM change of two semitones, and find the start and end values where it's lowest, even if they're both fractions. I think?

Yes -- you'd have an error value for the starting bpm, and another error value for `bpm * ratio`

, and you would want to minimize the sum.

The tricky thing mathematically is that the error function is piecewise -- every time you jump to the next round in point, it's a different segment. So calculus-based methods for continuous functions couldn't be used directly.

Playing around with it in SuperCollider, though (not Pd, for two reasons: 1/ SC has double-precision floats, 2/ Pd is clumsy at array math):

```
~error = { |a, b, factor = 0.001|
(a absdif: a.round(factor))
+
(b absdif: b.round(factor))
};
r = -2.midiratio;
f = 69;
// let's test 0.005 bpm on either side .. 1001 total
a = (f - 0.005, f - 0.00499 .. f + 0.005);
b = a * r; // slower bpms
c = ~error.(a, b); // SC auto-expands math ops to arrays!
c.plot;
c.minIndex; // 500
a[c.minIndex] // 68.999999999994 --> 69
```

So your "good" one is as good as that range is going to get.

Now, if you do the same thing for f = 77, the minIndex is 700 -- not 500 in the middle! -- and a[c.minIndex] = 77.001999999992 or basically 77.002.

Then:

((77.002 * r) absdif: round(77.002 * r, 0.001)) = 1.6905725900074e-05

((77.000 * r) absdif: round(77.000 * r, 0.001)) = 0.00020129683781533

So that shift of 0.002 bpm makes a *huge* difference.

This test also shows there's no benefit in checking "original" bpm that are not rounded to 0.001. So change it to `a = (f - 0.005, f - 0.004 .. f + 0.005);`

. EDIT: In that case, the "a" term in the ~error function will be 0 (if factor = 0.001) so you could simplify to:

```
~error = { |a, factor = 0.001|
a absdif: a.round(factor)
};
// and...
c = ~error.(b);
```

... which then does come back around to the problem that jameslo linked.

It's a brute-force technique but should work for the purpose.

hjh

]]>