atk-sc3 icon indicating copy to clipboard operation
atk-sc3 copied to clipboard

Implement meaningful error for Float -asHoaOrder ?

Open dyfer opened this issue 2 years ago • 13 comments

In case when one miscalculates the number of parameters in Hoa* ugens, one might pass a float to the order argument. The resulting error ERROR: Message 'asHoaOrder' not understood. was confusing to me at first.

I'm not saying that floats should be allowed (e.g. using .asInteger), since this could lead to silently erroneous behavior. However, I was wondering if -asHoaOrder could be implemented for Floats to throw a more meaningful message? (like HoaOrder needs to be an Integer).

Example:

x.free; x = {HoaEncodeDirection.ar(DC.ar(0), 0, 0, AtkHoa.refRadius, 1)}.play;
//                                              ^ one parameter too many
// we end up with AtkHoa.refRadius as the "order"

Result:

ERROR: Message 'asHoaOrder' not understood.
RECEIVER:
   Float 1.500000   00000000 3FF80000
ARGS:
(...)
^^ ERROR: Message 'asHoaOrder' not understood.
RECEIVER: 1.5

@joslloand I'd be happy to submit a PR adding an Error message for Float -asHoaOrder if you think this is a desired feature.

dyfer avatar Mar 02 '22 00:03 dyfer

We might consider a test like

// pseudocode
Float:-asHoaOrder
if(myFloat == myFloat.asInteger) {^myFloat} {error("Fractional orders aren't supported")} 

If fractional orders make sense in other contexts (analyzing effective decoder orders, for example), there may need to be more plumbing involved to isolate cases where fractional orders are not allowed.

mtmccrea avatar Mar 02 '22 11:03 mtmccrea

Hm... I like the idea of accepting float representations of (integer) order as general, but I also remember that comparing equality of floats is generally not a good idea. Maybe something along the lines of what UnitTest -assertArrayFloatEquals do would be more accurate?

if((myFloat - myFloat.asInteger).abs <= someSmallNumber) {^myFloat} {error("Fractional orders aren't supported")} 

Actually... looking at this I see that HoaOrder can be instantiated with a float:

HoaOrder(1.2); // no error

Of course it would error out later on when trying to access other methods.

With this in mind, I think that it would be better to move the check for float to HoaOrder, so that any possible future handling of floats is resolved there, and move -asHoaOrder to SimpleNumber instead of Integer. What do you think?

dyfer avatar Mar 02 '22 21:03 dyfer

if((myFloat - myFloat.asInteger).abs <= someSmallNumber) {^myFloat} {error("Fractional orders aren't supported")}

good idea!

I suppose the rest of the implementation question comes down to the fundamental question: should HoaOrder support float representations for order? What do you think @joslloand ?

mtmccrea avatar Mar 05 '22 11:03 mtmccrea

Somewhat relatedly, we have used float tolerance elsewhere: https://github.com/ambisonictoolkit/atk-sc3/blob/94b336028ec30434fb97750886ceb239a88790fc/Classes/Tests/AtkTests.sc#L58 Maybe we should promote this value to an Atk class var?

mtmccrea avatar Mar 05 '22 11:03 mtmccrea

I suppose the rest of the implementation question comes down to the fundamental question: should HoaOrder support float representations for order? What do you think @joslloand ?

Just to be clear, I'm not saying it should support it necessarily, but if it won't work properly with a float (i.e. the current situation), it should check for it at the initialization time...

dyfer avatar Mar 05 '22 21:03 dyfer

Yep, you're right to point out the inconsistency between instantiating HoaOder with a float, and -asHoaOrder not responding to a float, it's just brought up higher level question :)

I'm suggesting that we might want to support fractional/float orders, but I haven't thought much about use cases for fractional orders. If the use cases are few, then maybe it's supported only in those contexts (effective decoder order, as mentioned before, or franctional-order beam patterns).

mtmccrea avatar Mar 06 '22 09:03 mtmccrea

I'm suggesting that we might want to support fractional/float orders,

I think that's good, and IMO this future direction would also support my suggestion to check for float inside HoaOrder (we would remove that check once HoaOrder is ready for floats) and have both Integers and Floats respond to -asHoaOrder.

dyfer avatar Mar 07 '22 00:03 dyfer

Argh! Thanks @dyfer ;-)

Actually, this brings up a few things to consider that can probably be divided into practical / theoretical AND authoring / analyzing.

On the practical side, for instantiating HOA streams, we can only have integer orders (in terms of number of coefficients... weighting is another matter... see below). Theoretically, these directly relate to a boxcar, e.g., truncation, windowing. In our practical system, we want to know how big the signal set should be, so having a check for @dyfer's original error would be a good idea:

x.free; x = {HoaEncodeDirection.ar(DC.ar(0), 0, 0, AtkHoa.refRadius, 1)}.play;
//                                              ^ one parameter too many
// we end up with AtkHoa.refRadius as the "order"

OK, so you asked for it ;-)

Having a closer look at HoaOrder and -asHoaOrder:

~orderTest = 1.5.asHoaOrder  // fails, only works for Integer

whereas:

~orderTest = HoaOrder.new(1.5)  // instantiates, but bad...(unless you know what you're doing)

Where the latter fails makes good sense:

~orderTest.indices  // fails
~orderTest.sph  // fails
~orderTest.normalisation  // fails
~orderTest.proxWeights(440, 1.0, AtkHoa.speedOfSound)  // fails

Where the latter succeeds makes some sense (if you know what you're doing):

~orderTest.size  // useful?
~orderTest.radiusAtFreq(440, AtkHoa.speedOfSound)  // useful?
~orderTest.spreadE  // useful?

I believe I left in the ability to declare floating point fractional orders as a way for expert users (actually, me) to have access to rule of thumb comparisons when designing crossovers, frequency dependent radial filters, etc.


More theory...

In practice, the idea of fractional orders are not super standardized in a general way... which makes some sense, as what we're really doing is smoothing in the spatial domain. What we're really talking about is different kinds of LP filtering of the SH.

We DO have two standardized LP frequency independent (truncated, fractional order) filters:

  • energy weighting
  • controlled opposites weighting

We ALSO have some specialized LP frequency dependent (fractional order) filters. These are the focalisation filters:

  • Regularised
  • (Butterworth) High Pass
  • Cosine
  • Sine

More practice...

If there is a limited use for expert users to instantiating a fractional instance of HoaOrder, why aren't we throwing a warning? Well... if we're attempting to design a custom focalisation filter (bin by bin), we'll have the post window filled with warnings... which won't be pretty. I'm fairly sure that's whey I didn't put one in.

And... my assumption was that novices would never go into the deep water.

So... some possible solutions:

  1. for pseudo-UGens, check and throw an error for floats
  2. update the SCHelp for HoaOrder to state fractional orders are accepted, but WARN not all instance methods are supported and/or throw in checks where not. (I think the checks could become more tedious... and I think I'd prefer to just warn users that they being handed a length of rope, which may result in their own hanging.)

joslloand avatar Mar 07 '22 20:03 joslloand

Thanks @joslloand My main point is that -asHoaOrder should be implemented for either SimpleNumber or for both Integer and Float, Afterwards we can deal with floating point orders as we see fit.

So... some possible solutions:

  1. for pseudo-UGens, check and throw an error for floats
  2. update the SCHelp for HoaOrder to state fractional orders are accepted, but WARN not all instance methods are supported and/or throw in checks where not. (I think the checks could become more tedious... and I think I'd prefer to just warn users that they being handed a length of rope, which may result in their own hanging.)

Either would be fine, with different caveats/amount of work.

I'd also suggest a possible 3rd option (not necessarily better, but different): 3. HoaOrder.new(order, allowFloat: false); which would throw an error if the order is float and the flag is default (false). Advanced users could then instantiate the class with the flag set to true if needed (in which case there would be no error and probably no warning either). SimpleNumer -asHoaOrder would call the default option and thus throw on float orders.

dyfer avatar Mar 07 '22 20:03 dyfer

I'd also suggest a possible 3rd option (not necessarily better, but different): 3. HoaOrder.new(order, allowFloat: false); which would throw an error if the order is float and the flag is default (false). Advanced users could then instantiate the class with the flag set to true if needed (in which case there would be no error and probably no warning either). SimpleNumer -asHoaOrder would call the default option and thus throw on float orders.

@dyfer, I'm feeling an allowFloat flag would be the most attractive option, as it makes everything explicit.

If moved to SimpleNumber, -asHoaOrder for a Float will end up being nearly as verbose. E.g.:

  • HoaOrder.new(3.2, true)
  • 3.2.asHoaOrder(true)

More verbose is fine for expert users, though!!


As an aside, by adding an allowFloat flag to HoaOrder, we'll lose the explicit symmetry w/ the related classes:

Another side thought, while it would be great to support mixed orders (another kind of fractional order) like the ADT does for decoding, practically this is a headache best left to the ADT!!!

joslloand avatar Mar 07 '22 20:03 joslloand

As an aside, by adding an allowFloat flag to HoaOrder, we'll lose the explicit symmetry w/ the related classes:

Again, not necessarily a better option, but we could consider having *allowFloat as a class variable for HoaOrder. Pros:

  • set once for a project etc
  • -asHoaOrder would not need a flag
  • symmetry with aforementioned classes

Cons:

  • lack of granularity
  • less explicit interface (i.e. if one sets it globally and forgets it, there are no checks in place)

dyfer avatar Mar 07 '22 21:03 dyfer

Having been away from SC for a while and returning now, I have to say the proliferation of method arguments or state flags to fork functionality doesn't feel great (looking back at some of my more elaborate class methods) making future development more cumbersome, especially with experimental/expert functionality like fractional orders.

Distilling a bit here, we know now that fractional/tapered orders is something to support. Am I correct that an implementation as SimpleNumber would mean all current functionality is retained and we can patch in context-specific safeguards?

@joslloand's helpful theory discussion points toward a division of instantiating stream channels and beam pattern design... For example, a new method could be added: SimpleNumber:-asHoaSignalOrder, which performs

if((myFloat - myFloat.asInteger).abs <= someSmallNumber) {^myFloat} {error("Fractional orders aren't supported")}

Not yet a fully flushed out idea, but seems like it's a good direction?

mtmccrea avatar Mar 08 '22 10:03 mtmccrea

Relatedly:

  1. While looking into this, i noticed that given the use of HoaUGen:*confirmOrder, it may be better called *confirmSignalOrder.
  2. We may be bumping into a limit of a definition, and perhaps it's asking too much of the concept of 'order', as is widely understood, to be fractional. There may be room for a new concept/term that draws from factional polynomial degrees, etc., the representation for which in the ATK would be more sandboxed in the 'expert' corners of the library. ... but that's a wider research topic ;)

mtmccrea avatar Mar 08 '22 10:03 mtmccrea