Bytebeat equations - dealing with zero

Hi everyone,

Back when I was working on hardware modules under the brand Microbe Modular, I created a module based on ByteBeat: Sounds generated from equations. The hardware that I was developing for was the Arduino Due.

My first tinkerings with VCV Rack development was to recreate that module. However, I immediately ran into issues. Where the Arduino seemed ambivalent to dividing by zero, or dividing into zero, or dealing with large numbers that rolled over in memory, the compiler used for VCV Rack was not so forgiving. Given the same equations that ran OK on the Arduino, VCV Rack crashed. I spent months exploring and fine tuning the equations for the original hardware module and Iā€™d love to port those to VCV Rack.

Does anyone have any recommendations? Iā€™ve thought about using Lua or some other scripting language, but I worry about the overhead (CPU usage) in that sort of thing. But Iā€™m open minded to any suggestions.

Here are some of the equations:

Thanks in advance, Bret

As far as I know divisiĆ³n by zero is not a thing you can do in math so is undefined behavior on c.

When invoking undefined behavior the compiler can do whatever. It may remove the division altogether. Or make the result of the divion 0, which is wrong. It depends on the compiler.

If you want to use the same equations you may have to reverse engeneer what the Arduino compiler does.

1 Like

Anything divided by 0 and 0 divided by anything is always going to be 0

1/n for n going to 0 is +INF

-1/n for n going to 0 is -INF

0/n for any n != 0 -> 0

0/0 undefined -> NAN

you want to test the smallest number you can accept (isNear function) as divider ā€œnā€ and in that case use your INF or NAN representation

2 Likes

While many compilers do that, Section 5.6 of the c+Ā±11 standard (and Andrew uses c++11 flags, although I donā€™t think this has changed in 14 or 17) says

The binary / operator yields the quotient, and the binary % operator yields the remainder from the division of the first expression by the second. If the second operand of / or % is zero the behavior is undefined.

Which means, alas, a divide by zero could be ā€˜anythingā€™.

If you want divide by or mod by zero to be defined to have a consistent behavior you have to catch them with if before hand alas. Or do something else clever. Looking at the code seems a few of the equations are potentially impacted by this risk.

2 Likes

Thinking a bit more on this: The ARM will give some result for a divide by zero. I donā€™t know what it is but it would be some representation of nan or inf. You are just consuming that bit wise.

So the way you could make this code portable is to do something like, on your original architecture, with your original compiler, write a program that does (basically)

Uint32_t a, b;
A = 1;
B = 0;
Printf( ā€œ%xā€, a/b )

And see the hex code. It will be something. Maybe it is 0xFFBC. Maybe it is something else. Try with a bunch of values and make sure it is consistent.

Then in your formulae where you have something like p1 / t & ( p2 >> 3 ) or what not you have that p1 / t there. If t is 0 you want to replace it with your hex. Otherwise you want to do the divide. But you know that p1 / t will give some value, even if it is undefined. So you could replace p1 / t with something like this

Uint32_t tnonzero = t ? 0xFFFF : 0
Uint32_t armnan = 0xFFBC; // the value you observe in the test

( ~tnonzero & armnan ) | ( tnonzero & ( p1 / t ) )

You get the idea. Basically put your guard inline and do only one if and a known value.

The mods will be a bit trickier. Iā€™m still thinking about a way to do t / t % 10 without a divide. Doesnā€™t seem like it should need one does it? But this bit wise math is always tricky to be intuitive about.

Also I havenā€™t run any code and donā€™t have an arm and am typing this from my ipad and it is free advice, so value this comment accordingly. Good luck!

2 Likes

Thanks Paul! Thatā€™s really clever. Iā€™m going to think about this more when I have a chance. I appreciate the brain power! :+1:

The integer division instruction div on x86 throws a ā€œdivide errorā€ CPU exception, and Linux/Mac/Windows catches this and kills the process. So yes, the behavior is defined on x86. The only way around this is to use a function

template <typename T>
T divSafe(T x, T y) {
	if (y == 0)
		return 0;
	return x / y;
}
3 Likes

Oh yeah if the undefined behavior happens to be throw an exception my trick wonā€™t work at all!

I vote for this answer.

Well at least an if! Looking at my pre-coffee comment you could replace the gnarly bit wise silliness which is guarding

p1 / t

With

( t ? p1 / t : divByZeroVal )

The if is really the salient factor, not the function right? (If the behavior is an exception so you canā€™t bit mask out the value).

Good news! By using functions, as Andrew suggested, I have reproduced my first bytebeat from the original module. The reset is going to be cake. I should have a fully functional bytebeat player based on my old Equation Composer done in a matter of days. Woo hoo! :robot::musical_note:

1 Like