but I think there’s a bug in the Tempo variable because it doesn’t change when used as a label on a controller bar button and is fixed at 120 bpm even when increasing or decreasing the tempo value. I think @brad better check this out.
I just tried this and it seems to work fine, but remember this variable reports the tempo of the master transport, which if you’ve selected a media file will be the media file tempo, not the metronome tempo.
I’m confused. When I change it to “2 * Tempo” I see no difference. It still just sets the tempo to 5bpm. I have no media file. The master transport is the metronome, and I have nothing loaded (fresh, blank song).
Edit: I think I figured it out. It seems to be treating the output of the Tempo variable as a string instead of a number. If I put “1+Tempo”, then the tempo changes from 50 to 150 because it prepends a “1” to the string “50”. To force it to be treated as a number, I can write “clamp(Tempo,0,1000)*2” and that seems to work. But this seems like it’s probably unintended behavior.
Hey Kevin, sorry, you were right about the string vs number so this binding was what I intended. It’s less complex than your solution but works the same I think.
Thanks, Dave. I thought about using int(), but that solution doesn’t work when the Tempo isn’t an integer. Fortunately, clamp() seems to work on floats.
For the next build (4181) I’ve added a double function that will do this more simply: double(Tempo).
(That’s double as in “double precision floating point number”, not double as in doubling the tempo as per your original question).
The reason the tempo is a string is that originally all these variables were only ever used in string interpolations eg: "The tempo is $(Tempo)". Then the expression engine was updated to also provide access to the string interpolation variables, but all the values had to stay as strings. So the expression engine has access to them, but you need to convert them to do any sort of math on them.
I guess I’m confused about how coercions work in the expression language. Why do prefix arithmetic operators like clamp() coerce strings to numbers, but infix arithmetic operators like + and * do not? And why is Tempo*2 equal to 5?
Does the string interpolator perform coercsions? I guess I always assumed that $(Tempo) was returning a double, which the string interpolator coerced to a string.
When the expression engine invokes a function call it knows the parameter types the function expects and will type convert arguments to make them fit.
With operators, the result type can depend on the argument types and it will type convert to the wider type. Mixing strings and numbers though gets weird because you can add strings to concatenate them, but you can’t multiply.
I think the solution for this is to internally introduce two representation of the non-string variable values - a value for use in the expression evaluation, and a value for use in string interpolation. The reason for this the string values needed for string interpolation can be quite different to the actual value (eg: gain values are internally double scale factors but are displayed as dB, some values are -1 to represent N/A and are displayed as “-” etc…)
With Tempo*2, I’d need to double check to be sure but suspect it’s evaluating to zero, but when assigning to the Tempo binding point, the minimum tempo supported by Cantabile is 5 so the zero is rounded up.
(Since I’m a language designer in my professional life, I can’t resist commenting…) Many dynamically typed languages use an underlying tagged-union representation, where the tag is an integer type-identifier. This allows dynamic types to be assigned to all values as a computation progresses, like raw integers, raw doubles, double scale factors (gains), strings, etc. Each function/operator can then choose an appropriate coercion regimen for its arguments, or a type-specific string representations when coercing to strings. The downside is that values become larger in memory (since tagged union structs are usually larger than register-bitwidth). But if everything is already a string, that wouldn’t be much of a change (overhead would be similar to adding one type-defining char to the head of each string value).
Yes, Cantabile needs something similar. I’m thinking to wrap any numeric values with special formatting as an object with a value property and a display ToString() method. To expression engine can use the value, the string interp can use ToString().
As you say they’re already reported as strings, so the overhead would be negligible.
You can use a binding like this one to do it. The secret is the using the expression of “double(Tempo)*2” in the result field. I used a controller bar button for the source trigger in this example.