The control logic for the various combinations of linear and logarithmic scales is nasty,
and the added path coverage indeed unveiled several bugs.
One of these issues, an unjustified limitation on the input value type of `asLogarithm`,
convinced me to refactor the conversion functions into free functions,
since they effectively ''always span the relation of two scales'' --
writing them as member function introduces an unnecessary asymetry.
As with all those C++ base types, unfortunately they are giving you
only rough size guarantees. For Lumiera, I defined short hand names
some time ago, notably because the explicit long X and short X qualifiers
do not work with ''constructor syntax'' for direct conversions.
But I choose the name `f128` at that time -- which might raise ill-guided
expectations, since the `long double` is not that long on relevant linux 64bit
platforms; it can just hold 64bit of mantissa at least however.
As replacement, I'll use `lflp` now, because it has also 4 letters,
looks somewhat weird (and thus catches attention) and points into the
right direction. I considered `lfloat` as alternative, but that looks
too much like `float`, visually...
There are some corner cases where the C++ number promotion rules can hurt us badly.
Devised a type selection logic to pick a suitable common numeric type explicitly,
using `std::common_type<T1,T2>` in most cases, yet handling explicitly the
case when the computation would wrap into unsigned; in such a mixed sign integral case,
we either use int64_t (when its precision suffices), or long double,
which can hold 64bit of mantissa without precision loss.
LIN -> LIN yet either additive or multiplicative
LOG -> LIN
LIN -> LOG
and different kinds of logarithms
all computations using a common intermediary numeric type,
preferably a suitably sized floating-point
The `join()`-operation allows to combine a value pulled from automation
to be combined with a local ''base value'' stored in the parameter.
When both the source and the target parameter use the same Scale,
this operation is simple; it becomes more tricky once they don't --
because then linear / logarithmic and type conversions must be handled.
I now decided to provide a generic implementation in the Scale (descriptor),
while the enclosing parameter shall handle the dispatch when connecting
to a opaque source with another Scale. And this generic implementation
relies on the same technique as was used for the parameter type conversions,
thus it should be factored out into a library header.
Scales are defined as descriptor records and will be used as const.
Besides some information functions, a Scale
* allows to conform a value to be compliant with the Scale's constraints
* can ''join'' and thus apply a ''feed'' on top of a ''base value'';
this is the foundation for applying an automation function, while
handling multiplicative and logarithmic cases properly