Closed tomcombriat closed 6 months ago
Memory usage change @ 3224268ddacd54c967453d6c9bed2d127825c487
Board | flash | % | RAM for global variables | % |
---|---|---|---|---|
STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103C8 |
:grey_question: -7480 - +16 | -11.41 - +0.02 | :green_heart: -16 - 0 | -0.08 - 0.0 |
arduino:avr:uno |
:green_heart: -2 - 0 | -0.01 - 0.0 | :small_red_triangle: 0 - +1 | 0.0 - +0.05 |
arduino:mbed_giga:giga |
:green_heart: -3416 - 0 | -0.17 - 0.0 | 0 - 0 | 0.0 - 0.0 |
arduino:samd:adafruit_circuitplayground_m0 |
:grey_question: -14476 - +8 | -5.52 - 0.0 | :green_heart: -4 - 0 | -0.01 - 0.0 |
esp8266:esp8266:huzzah |
N/A | N/A | N/A | N/A |
rp2040:rp2040:rpipico |
:grey_question: -2464 - +8 | -0.12 - 0.0 | :green_heart: -48 - 0 | -0.02 - 0.0 |
If you want to have a look, the example I just added was precisely the kind of things I had in mind with FixMath. The main usage is in combination with MIDI and except than the fromRaw()
that is a bit heavy syntax (but impossible to simplify it if we want to keep the int
constructor), that might be replaced with SFixMath<0,7>(aVibrato.next(),true)
, I think it looks fairly clean.
Will try to change some other examples soon.
Memory usage change @ 9bdf4102c72cfd9da6d92f45be870a6ed1d501c2
Board | flash | % | RAM for global variables | % |
---|---|---|---|---|
STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103C8 |
:grey_question: -7480 - +16 | -11.41 - +0.02 | :green_heart: -16 - 0 | -0.08 - 0.0 |
arduino:avr:uno |
:green_heart: -2 - 0 | -0.01 - 0.0 | :small_red_triangle: 0 - +1 | 0.0 - +0.05 |
arduino:mbed_giga:giga |
:green_heart: -3416 - 0 | -0.17 - 0.0 | 0 - 0 | 0.0 - 0.0 |
arduino:samd:adafruit_circuitplayground_m0 |
:grey_question: -14476 - +8 | -5.52 - 0.0 | :green_heart: -4 - 0 | -0.01 - 0.0 |
esp8266:esp8266:huzzah |
N/A | N/A | N/A | N/A |
rp2040:rp2040:rpipico |
:grey_question: -2464 - +8 | -0.12 - 0.0 | :green_heart: -48 - 0 | -0.02 - 0.0 |
Memory usage change @ 89ddc913778a338e90d26d8bfba701fc794875d8
Board | flash | % | RAM for global variables | % |
---|---|---|---|---|
STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103C8 |
:grey_question: -7480 - +16 | -11.41 - +0.02 | :green_heart: -16 - 0 | -0.08 - 0.0 |
arduino:avr:uno |
:green_heart: -2 - 0 | -0.01 - 0.0 | :small_red_triangle: 0 - +1 | 0.0 - +0.05 |
arduino:mbed_giga:giga |
:green_heart: -3416 - 0 | -0.17 - 0.0 | 0 - 0 | 0.0 - 0.0 |
arduino:samd:adafruit_circuitplayground_m0 |
:grey_question: -14476 - +8 | -5.52 - 0.0 | :green_heart: -4 - 0 | -0.01 - 0.0 |
esp8266:esp8266:huzzah |
N/A | N/A | N/A | N/A |
rp2040:rp2040:rpipico |
:grey_question: -2464 - +8 | -0.12 - 0.0 | :green_heart: -48 - 0 | -0.02 - 0.0 |
If you want to have a look, the example I just added was precisely the kind of things I add in mind with FixMath. The main usage is in combination with MIDI and except than the
fromRaw()
that is a bit heavy syntax (but impossible to simplify it if we want to keep theint
constructor), that might be replaced withSFixMath<0,7>(aVibrato.next(),true)
, I think it looks fairly clean.
Looks pretty clean to me, too. But also, yes, the line inside updateAudio() looks a bit scary. The alternative without fromRaw does not really seem too much better, to me. Some ideas for brainstroming:
SFixMath<0,7> Oscil::nextSFix()
. Downsides: Would be needed everywhere, may get confusing if (when) Oscil gains the ability to work on more than 8-bit samples.typedef FractSample SFixMath<0,7>
. Would avoid some brackets, but not much else.SFixMath<0,7> sampleToSFix(int8_t)
?Some ideas for brainstroming:
And another one:
(SFixMath<0,7> (osc.next()).sR<7>()
But not sure it is way better… I was thinking to do a convenient function but that guides toward the having the whole sample in fractional, whereas some situations can very likely benefit from having samples as, say, SFixMath<5,2>
.
The more logical way, for me would be (my other suggestion in two lines):
SFixMath<7,0> toto oscil.next();
auto tata = toto.sR<7>();
but will continue thinking about it…
Another one:
template<int NI=0, typename T> auto toSFix(T val) {
return SFixMath<NI, sizeof(T)*8-NI-1>::fromRaw(val);
}
auto toto = toSFix(aSin.next()); // SFixMath<0, 7>
auto tata = toSFix<5>(aSin.next()); // SFixMath<5, 2>
(Alternatively make NF, instead of NI, available as parameter. Not sure, which is less confusing.)
And yet another (which is inspired from yours but allows for smaller types if needed, for instance NI=2; NF=4
):
template<byte NI, byte NF> auto toSFix(T val) {
return SFixMath<NI, NF>::fromRaw(val);
I think that's a way to go, especially because the ::
is not encountered very often (and never in Mozzi), so I am a bit afraid of the troubles in can bring to users. Maybe toSFixRaw
instead also, to make it clear this is the raw value.
Note that we can actually have both (which will "look" like a single template function with two optional parameters). Yours definitely makes sense, as it allows full control. Mine, I feel, may also be justified, as it simplifies the (important?) use case of converting from Oscil/Sample/filters, even if it hides away the bit-counting.
Imagine, also, we had a template<int NF> AudioOutput_t MonoOutput::fromFraction(SFixMath<0, NF>);
to go with that. This might then actually open entirely new use-cases. E.g.:
int8_t a = aSin.next();
int8_t b = kLFO.next();
int8_t c = master_volume;
//return MonoOutput::fromNbit(24, a*b*c); // Oops on AVR: Auto-promotion only goes up to 16 bit ints
return MonoOutput::fromFraction(toSFix(a)*toSFix(b)*toSFix(c)); // Let Mozzi handle the bit-counting
One step further, imagine the potential of toSFix(Oscil.next())
, when in a future version of Mozzi, Oscils will not be limited to 8 bits.
(Yes, this also has the potential to run into even 64bit calculations, unexpectedly, but then, this has always been an area full of pitfalls, anyway.)
I think that's a way to go, especially because the
::
is not encountered very often (and never in Mozzi), so I am a bit afraid of the troubles in can bring to users. MaybetoSFixRaw
instead also, to make it clear this is the raw value.
Yes, the naming should be clear (but if possible not too cumbersome). "toSFraction" came to my mind. This should remove any expectation that a whole number is returned. OTOH it would also remove the obvious association with SFixMath, so not sure, whether that's a good idea, overall.
Edit: Well, ok, my example fails on signedness. You still get the idea.
Very good points, basically we could use this kind of approach everywhere when a non-overflowing integer is useful. The autosizing is a good point, especially as Mozzi's own object do use the whole range of the integers they are based on. Powerful and dangerous tool…
I do not have lot of time these days but will try to put that together, I am just concerned (but without trying) that the compiler might complain that the templates should be clearer, even if all arguments are actually known, so that:
auto toto = toSFraction(aSin.next());
would need to be
auto toto = toSFraction<>(aSin.next());
but I haven't tried.
toSFraction
sounds good to me. Of course, a similar helper toSInt
would also be of the party I think. I also think that these names would be misleading for cases which are mixed like:
auto tata = toSFix<5>(aSin.next()); // SFixMath<5, 2>
where I think toSFix
sounds non-misleading (where toSFraction<5>
does to me: it has integer bits in the end, it is not a fraction). This is probably the trickier one to make easy to understand for users, but I am actually not sure it is actually useful… Might try to leave it out at first, and see what it gives!
For audioOutput
this is also a very good idea, I would push even further with a return MonoOutput::fromSFix
(or fromFix
), as basically, the only thing Mozzi needs to know, is NI+NF
, which would allow the case (from your example) a+b+c
to be handle in an exact same way. In combination with Oscils, as you suggested, this seems very powerful, and toSFraction
will be more useful there than toSInt
as the range will effectively be the same for all oscillators, just with different precision.
Memory usage change @ d3f97026285d2c1ba7c13b7d21c27d55d91154a3
Board | flash | % | RAM for global variables | % |
---|---|---|---|---|
STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103C8 |
:grey_question: -8 - +16 | -0.01 - +0.02 | :small_red_triangle: 0 - +4 | 0.0 - +0.02 |
arduino:avr:uno |
:green_heart: -2 - 0 | -0.01 - 0.0 | :small_red_triangle: 0 - +1 | 0.0 - +0.05 |
arduino:mbed_giga:giga |
0 - 0 | 0.0 - 0.0 | 0 - 0 | 0.0 - 0.0 |
arduino:renesas_uno:minima |
:grey_question: -512 - +16 | -0.2 - +0.01 | :small_red_triangle: 0 - +8 | 0.0 - +0.02 |
arduino:samd:adafruit_circuitplayground_m0 |
:grey_question: -12 - +16 | -0.0 - +0.01 | :small_red_triangle: 0 - +4 | 0.0 - +0.01 |
esp8266:esp8266:huzzah |
N/A | N/A | N/A | N/A |
rp2040:rp2040:rpipico |
:grey_question: -520 - +16 | -0.02 - 0.0 | 0 - 0 | 0.0 - 0.0 |
Memory usage change @ aa90380599eb7e6e6aaff4ff31f9f5d1948605ab
Board | flash | % | RAM for global variables | % |
---|---|---|---|---|
STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103C8 |
:grey_question: -8 - +16 | -0.01 - +0.02 | :small_red_triangle: 0 - +4 | 0.0 - +0.02 |
arduino:avr:uno |
:green_heart: -2 - 0 | -0.01 - 0.0 | :small_red_triangle: 0 - +1 | 0.0 - +0.05 |
arduino:mbed_giga:giga |
0 - 0 | 0.0 - 0.0 | 0 - 0 | 0.0 - 0.0 |
arduino:renesas_uno:minima |
:grey_question: -512 - +16 | -0.2 - +0.01 | :small_red_triangle: 0 - +8 | 0.0 - +0.02 |
arduino:samd:adafruit_circuitplayground_m0 |
:grey_question: -12 - +16 | -0.0 - +0.01 | :small_red_triangle: 0 - +4 | 0.0 - +0.01 |
esp8266:esp8266:huzzah |
N/A | N/A | N/A | N/A |
rp2040:rp2040:rpipico |
:grey_question: -520 - +16 | -0.02 - 0.0 | 0 - 0 | 0.0 - 0.0 |
Memory usage change @ cd805d5c15a01d18808110b0f5140a99ea4ab71f
Board | flash | % | RAM for global variables | % |
---|---|---|---|---|
STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103C8 |
:grey_question: -8 - +16 | -0.01 - +0.02 | :small_red_triangle: 0 - +4 | 0.0 - +0.02 |
arduino:avr:uno |
:green_heart: -2 - 0 | -0.01 - 0.0 | :small_red_triangle: 0 - +1 | 0.0 - +0.05 |
arduino:mbed_giga:giga |
0 - 0 | 0.0 - 0.0 | 0 - 0 | 0.0 - 0.0 |
arduino:renesas_uno:minima |
:grey_question: -512 - +16 | -0.2 - +0.01 | :small_red_triangle: 0 - +8 | 0.0 - +0.02 |
arduino:samd:adafruit_circuitplayground_m0 |
:grey_question: -12 - +16 | -0.0 - +0.01 | :small_red_triangle: 0 - +4 | 0.0 - +0.01 |
esp8266:esp8266:huzzah |
N/A | N/A | N/A | N/A |
rp2040:rp2040:rpipico |
:grey_question: -520 - +16 | -0.02 - 0.0 | 0 - 0 | 0.0 - 0.0 |
Okay, I added .toUFraction()
, toUInt()
and the same for SFix
. Also made a general inverse and simplified the current implementation of .invAccurate()
.
Now, something bugs me, but I am not sure there is an easy solution (but maybe ?). Let's us take the following example:
UFixMath<8,0> a, b, c, d;
auto toto = a+b+c+d; // leads to UFixMath<11,0> even though only 10 bits are actually needed
auto tata = (a+b) + (c+d); // leads correctly to UFixMath<10,0>
The problem here is that, to ensure non-overflow, promotion to the next bigger number of bits is needed, but adding two different types actually lets an headroom: UFixMath<9,0> + UFixMath<8,0>
does not fill the resulting UFixMath<10,0>
completely, a value of up to 255 (8 bits = (9-8)*8) can still can into that.
Now, numbers could remember the headroom they have left, but I do not see how that could be applied to change the returning type of say, the +
operator between two different FixMath, even thought the headroom is known at compile time. Any idea @tfry-git ? Or maybe I am overthinking it, and just good documentation about parenthesing is needed.
For reference, here is the current addition operator:
template<byte _NI, byte _NF>
UFixMath<MAX(NI,_NI)+1,MAX(NF,_NF)> operator+ (const UFixMath<_NI,_NF>& op) const
{
constexpr byte new_NI = MAX(NI, _NI) + 1;
constexpr byte new_NF = MAX(NF, _NF);
typedef typename IntegerType<MozziPrivate::uBitsToBytes(new_NI+new_NF)>::unsigned_type return_type;
UFixMath<new_NI,new_NF> left(*this);
UFixMath<new_NI,new_NF> right(op);
return_type tt = return_type(left.asRaw()) + right.asRaw();
return UFixMath<new_NI,new_NF>(tt,true);
}
would need to have its return type depending on the headroom remaining in the biggest of the two initial types. Would probably another big knot to implement, if possible…
Now, numbers could remember the headroom they have left, but I do not see how that could be applied to change the returning type of say, the + operator between two different FixMath, even thought the headroom is known at compile time.
I think the only way to do that would be as third template parameter (which could, obviously, default to 0 if not specified).
As for the follow-up question, I am not sure. My initial feeling was that you may be overthinking it, indeed, considering it will probably be non-typical to add a large amount of numbers, and the drawbacks of one bit too many, here or there, did not seem to bad. OTOH, considering the vision that SFixMath might actually provide a full solution to bit-counting inside updateAudio(), we'd really want to be as accurate as possible.
I'm a bit undecided, here. How much pain would it be to carry a headroom parameter through all operators? And how much confusion will it cause?
Thinking about alternatives, I believe the following additions may be useful in their own right (sorry for adding ever more complexity to this PR):
In your example: auto tata = (a+b+c+d).clip(10,0);
.
I think the only way to do that would be as third template parameter (which could, obviously, default to 0 if not specified).
Yes, this is also the conclusion I came to. Fortunately, only NI
is impacted, not NF
.
I'm a bit undecided, here. How much pain would it be to carry a headroom parameter through all operators? And how much confusion will it cause?
Honestly, I do not pronounce myself on pain involved anymore… I started this thinking that would be a quick job, but it turns out that it is not that simple IMO, especially correctly figuring out the maths. A problem is that, there is also not a lot on that around, some people probably figured all of this out… 40 years ago. Anyway, I will try to see, maybe in another branch, and see how it goes. Regarding confusion, my idea would be to completely hide that to the user (but with documentation for the developers), in order to make this as carefree as possible.
The functions you propose could also be useful but as a clarification, would they actually check the value before trimming or clipping? Or would a complete templated approach, working on ranges, be what you have in mind? Note that clip can be done already with casting: UFixMath<10,0> toto = a+b+c+d;
(except when there is an overflow maybe) , and trim with .sR<>()
and .sL<>()
.
I will try to make my mind about it… Will continue with the examples first as they usually bring some good usage cases.
sorry for adding ever more complexity to this PR)
Sorry that it takes that long! I feel a bit bad that Mozzi2 is a bit hang on that… I did not realized at first that this would turn out that big! But I also quite like the way this module behaves now. Thanks for taking the time to answer my questionings!
The functions you propose could also be useful but as a clarification, would they actually check the value before trimming or clipping?
TBH, I may not have quite thought this through to the end, and in consequence it may be a good idea to ignore this bit, for the time being. It falls into the category of "can be added at any time", after all.
And yes, I had also severely underestimated the complexity of this PR. But then that's because I also underestimated the complexity of the problems that it solves. This is going to be good!
Memory usage change @ 9bfe86455f5b0bf63e6efcca875a95be3bc4bfe0
Board | flash | % | RAM for global variables | % |
---|---|---|---|---|
STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103C8 |
:grey_question: -8 - +16 | -0.01 - +0.02 | :small_red_triangle: 0 - +4 | 0.0 - +0.02 |
arduino:avr:uno |
:green_heart: -2 - 0 | -0.01 - 0.0 | :small_red_triangle: 0 - +1 | 0.0 - +0.05 |
arduino:mbed_giga:giga |
:green_heart: -64 - 0 | -0.0 - 0.0 | 0 - 0 | 0.0 - 0.0 |
arduino:renesas_uno:minima |
:grey_question: -512 - +16 | -0.2 - +0.01 | :small_red_triangle: 0 - +8 | 0.0 - +0.02 |
arduino:samd:adafruit_circuitplayground_m0 |
:grey_question: -12 - +16 | -0.0 - +0.01 | :small_red_triangle: 0 - +4 | 0.0 - +0.01 |
esp8266:esp8266:huzzah |
N/A | N/A | N/A | N/A |
rp2040:rp2040:rpipico |
:grey_question: -520 - +16 | -0.02 - 0.0 | 0 - 0 | 0.0 - 0.0 |
Memory usage change @ 05fec8a7f6b53c50e8bf0d58548a141d138b0bef
Board | flash | % | RAM for global variables | % |
---|---|---|---|---|
STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103C8 |
:grey_question: -8 - +16 | -0.01 - +0.02 | :small_red_triangle: 0 - +4 | 0.0 - +0.02 |
arduino:avr:uno |
:green_heart: -2 - 0 | -0.01 - 0.0 | :small_red_triangle: 0 - +1 | 0.0 - +0.05 |
arduino:mbed_giga:giga |
:green_heart: -64 - 0 | -0.0 - 0.0 | 0 - 0 | 0.0 - 0.0 |
arduino:renesas_uno:minima |
:grey_question: -512 - +16 | -0.2 - +0.01 | :small_red_triangle: 0 - +8 | 0.0 - +0.02 |
arduino:samd:adafruit_circuitplayground_m0 |
:grey_question: -12 - +16 | -0.0 - +0.01 | :small_red_triangle: 0 - +4 | 0.0 - +0.01 |
esp8266:esp8266:huzzah |
N/A | N/A | N/A | N/A |
rp2040:rp2040:rpipico |
:grey_question: -520 - +16 | -0.02 - 0.0 | 0 - 0 | 0.0 - 0.0 |
I think this looks quite good. There might be some possible improvements on the sides of constexpr
and automatic testing but I think this is already a big baby.
Outside of the code itself, and before merging, I would like to gather your opinion on this: I think this library could also be quite useful in other context than Mozzi (at least, I am sure I would use it for other purposes). I think there are basically three options:
I am not in favor for 3. because of the difficulties to maintain such a duplicated code, but I would be quite up to try to set 2. up, as a separate repo with you as collaborator(s). As this was developed in the context of Mozzi, I of course would like your opinions about it and the decision is taken together.
Memory usage change @ 4f97ef064cd9420f0d9cb2af63f08df2ef53b7f3
Board | flash | % | RAM for global variables | % |
---|---|---|---|---|
STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103C8 |
:grey_question: -8 - +16 | -0.01 - +0.02 | :small_red_triangle: 0 - +4 | 0.0 - +0.02 |
arduino:avr:uno |
:green_heart: -2 - 0 | -0.01 - 0.0 | :small_red_triangle: 0 - +1 | 0.0 - +0.05 |
arduino:mbed_giga:giga |
:green_heart: -64 - 0 | -0.0 - 0.0 | 0 - 0 | 0.0 - 0.0 |
arduino:renesas_uno:minima |
:grey_question: -512 - +16 | -0.2 - +0.01 | :small_red_triangle: 0 - +8 | 0.0 - +0.02 |
arduino:samd:adafruit_circuitplayground_m0 |
:grey_question: -12 - +16 | -0.0 - +0.01 | :small_red_triangle: 0 - +4 | 0.0 - +0.01 |
esp8266:esp8266:huzzah |
N/A | N/A | N/A | N/A |
rp2040:rp2040:rpipico |
:grey_question: -520 - +16 | -0.02 - 0.0 | 0 - 0 | 0.0 - 0.0 |
Thanks for your comments @tfry-git !
Would it make sense to rename the classes as UFix~Math~/SFix~Math~?
Indeed, I think it looks better. This is done.
Outside of the code itself, and before merging, I would like to gather your opinion on this: I think this library could also be quite useful in other context than Mozzi (at least, I am sure I would use it for other purposes).
@tfry-git , @sensorium any opinions on this?
Memory usage change @ aff581994d85857a7a705ffd9c6200878fae0c0f
Board | flash | % | RAM for global variables | % |
---|---|---|---|---|
STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103C8 |
:grey_question: -8 - +16 | -0.01 - +0.02 | :small_red_triangle: 0 - +4 | 0.0 - +0.02 |
arduino:avr:uno |
:green_heart: -2 - 0 | -0.01 - 0.0 | :small_red_triangle: 0 - +1 | 0.0 - +0.05 |
arduino:mbed_giga:giga |
:green_heart: -64 - 0 | -0.0 - 0.0 | 0 - 0 | 0.0 - 0.0 |
arduino:renesas_uno:minima |
:grey_question: -512 - +16 | -0.2 - +0.01 | :small_red_triangle: 0 - +8 | 0.0 - +0.02 |
arduino:samd:adafruit_circuitplayground_m0 |
:grey_question: -12 - +16 | -0.0 - +0.01 | :small_red_triangle: 0 - +4 | 0.0 - +0.01 |
esp8266:esp8266:huzzah |
N/A | N/A | N/A | N/A |
rp2040:rp2040:rpipico |
:grey_question: -520 - +16 | -0.02 - 0.0 | 0 - 0 | 0.0 - 0.0 |
Outside of the code itself, and before merging, I would like to gather your opinion on this: I think this library could also be quite useful in other context than Mozzi (at least, I am sure I would use it for other purposes).
@tfry-git , @sensorium any opinions on this?
As discussed in private mail, I do think that makes sense, indeed. I'll add that I'm not sure, what will be the best procedure:
I imagine that Mozzi is not a bad breeding ground for the time being (as it includes auto-compilation infrastructure and a "playground" of examples to test the FixMath concepts on). So, in other words, I think it may (or may not) make sense to delay the "split-out" for another little while. What's your plan on this?
As discussed in private mail, I do think that makes sense, indeed. I'll add that I'm not sure, what will be the best procedure
Honestly I do not really know, especially how the automatic testing will coop with a dependency. If I am correct, the Arduino IDE does fare well with duplicated libraries hence my plan for now was to start another repo (probably today), and get it into the library manager while continuing to work on this PR (Line
requires a bit of work, as well as finishing the examples). It will become more gigantic but t least it will be on one thing.
Once the other one is on, we can try to remove the source file for FixMath here and take it from there? What do you think?
I would be in favor of merging this into Mozzi2 if we are satisfied with the current implementation (which could change).
There is still work to do, including changes in some classes (Line
, Smooth
) which might imply some design choices, deserving their own PR. Also all change on example is now going to lead to a conflict (I could merge Mozzi2 in here though, but I have the feeling this PR is big enough).
That does not preclude the design of FixMath to change of course.
Memory usage change @ e2bd6884a15b552b8104ff0b90d0b374e8ce0ac8
Board | flash | % | RAM for global variables | % |
---|---|---|---|---|
STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103C8 |
:grey_question: -8 - +16 | -0.01 - +0.02 | :small_red_triangle: 0 - +4 | 0.0 - +0.02 |
arduino:avr:mega |
:green_heart: -2 - 0 | -0.0 - 0.0 | :small_red_triangle: 0 - +1 | 0.0 - +0.01 |
arduino:avr:uno |
:green_heart: -2 - 0 | -0.01 - 0.0 | :small_red_triangle: 0 - +1 | 0.0 - +0.05 |
arduino:mbed_giga:giga |
:green_heart: -64 - 0 | -0.0 - 0.0 | 0 - 0 | 0.0 - 0.0 |
arduino:renesas_uno:minima |
:grey_question: -512 - +16 | -0.2 - +0.01 | :small_red_triangle: 0 - +8 | 0.0 - +0.02 |
arduino:samd:adafruit_circuitplayground_m0 |
:grey_question: -12 - +16 | -0.0 - +0.01 | :small_red_triangle: 0 - +4 | 0.0 - +0.01 |
esp8266:esp8266:huzzah |
N/A | N/A | N/A | N/A |
rp2040:rp2040:rpipico |
:grey_question: -520 - +16 | -0.02 - 0.0 | 0 - 0 | 0.0 - 0.0 |
teensy:avr:teensy36 |
:grey_question: -8 - +16 | -0.0 - 0.0 | 0 - 0 | 0.0 - 0.0 |
teensy:avr:teensy41 |
N/A | N/A | N/A | N/A |
This is my results of trying to get a nice FixMath working. This is still working in progress: it is quite messy, ~only
unsigned
is done and except for casts, only the multiplication is implemented~ but that gives the idea and allow for some discussion before I go further.(@tfry-git you asked for templates, well you'll be served, and macros too ;) ).
In brief:
This defines a new fixmath type2 for unsigned:
UFixMath2<N,n>
andSFixMath2<N,n>
whereN
is the number of integer bits andn
the number of fractional bits.These two values can be anything, but value safety is only ensured up to 63 bits total (I think this is enough). A static assert prevents going further.
Any
UFixMath2<N,n>
can be casted into anyUFixMath2<N2,n2>
, but if the initial value cannot be represented in the new type you are running into overflow, just like with standard types.After fiddling for quite some time, I changed the paradigm from what was discussed in #207: the multiplication between
UFixMath2<N,n>
andUFixMath2<N1,n1>
returns aUFixMath2<N+N1,n+n1>
. Basically, (after nearly skipping a night on it), I came to the conclusion that if you return to the first type of the multiplication (as discussed in #207) you have to either:The current paradigm allows for optimisation if you choose the input types so that it fits, but with no margin. The results are as accurate as they can be and won't overflow. If you do not need precision you can always cast them lower prior to multiplication (faster), or after if you want something precise but with fewer bits:
In the example above, the choice of the type for
toto
is not smart at all as the value would fit in 8 bits integral part which is why the return type is sub-optimal.So these types are quite easy to optimize if you know the expected range of your values, then you can choose the smallest container that accommodate them, with the number of fractional bits setting the precision.
Methods (so far)
float
,double
UFixMath2
UFixMath2<8,8> tata(10);
UFixMath2<8,8> tata(10,as_frac=true);
UFixMath2.asFloat()
returns the value as floatUFixMath2.getInt()
returns the internal integer.The internal integer is using
IntegerType.h
to find the correct container. The formula is a bit weird:typedef typename IntegerType<((NI+NF-1)>>3)+1>::unsigned_type internal_type ;
, but the-1, +1
ensures that your container is big enough.Other methods will to be implemented if we agree with the current layout already. On that, can we assume the compiler to be smart enough to transform
int a/2
intoint a>>1
and skip the shifting operators? I read somewhere that it should but if someone knows for sure. Integration into Mozzi, especially for theOscil.setFreq()
are also on the list.I am also wondering if the division between the FixPoints should be implemented. Once again, assumptions need to be made to shift left the left hand side of the division. Or provide a crude division that does not do it (and let the users cast up if needed). I do not think I ever used fixed point division in any of my Mozzi codes though, might mislead more than help. Of course, division by an integer is absolutely needed but the case is nearly trivial if we assume no shift before.
It is a bit messy for now, tried quite a few things last night on the pre-shift way before convincing myself that this is not satisfying. Will clean up soon.
Of course, some standard types (like Q8n8) will be typedef'd in the end.
Welcoming comments and suggestions as always!
Planned and acheived
Notes
Any operations between FixType is supposed to be safe, in terms of overflow and precision. However, any operation with other types, like
int
is not. This is basically to avoid these types to get promoted to easily for instance:I think this will push to use only FixType which, might not be a bad thing as long as people do not abuse on the container size because they can't overflow.
One idea for that might be to allow automatic construction from
const int
(and affiliated), where the constructor (or an affiliated function) determines theNI
automatically to the smallest needed value. Ideally, it would be neat to do:and the compiler finds itself that NF=2, but not sure it is possible.
Example code (without interaction with Mozzi yet)