KSPMath V450
KSPMath V450
KSPMath V450
** PLEASE NOTE **
In order to use the Math Library, you must invoke SetMathMode(option_list) in the
initialization callback (ICB) of your host script. Also be aware that you cannot
invoke most library functions from the ICB as with some earlier versions.
However, if your script needs to invoke Math routines as a part of
initialization,
it's easy to do this by simply including PI in the option_list of SetMathMode.
This will cause the pgs callback to be triggered immediately after the ICB exits
and you can then invoke any kind of function in the pgs. Please see the User's
Guide Addendum for a detailed discussion of post-ICB initialization.
----------------------------------------------------------------------------------
All functions automatically declare any needed data structures so all you
need do to use any Library function is to simply reference it in your host
script. However, as stated above, you must always include SetMathMode in
your script's ICB and you must include HX in your options_list if you want
to use CVHex.
OUTPUT PARAMETERS: Beginning with V450, the library uses the new KSE
return-value feature for all single-output-value functions. Since most
library routines are multi-line functions, you can only reference them
as a single expression on the right side of an assignment statement.
However, there are a few 1-line functions which can be referenced
anywhere. Nothing terrible will happen if you reference a multi-line
function the wrong way because the KSE will flag it for you.
V450 of the Math Library includes a fast-mode option for the slower library
routines. The fast-mode relies on a few large arrays that are automatically
loaded from .nka files. Again, none of these things are added to your code
unless you actually use them. The library also provides several utility
functions that make it easy for you to write or re-write the .nka files.
Consult the header comments for each routine to see whether a fast mode version
is available and what option code(s) are required in SetMathMode in order to
utilize the fast mode. For details about how to use the Fast-Mode option codes
and how to produce and use the supporting .nka files, please see the User's
Guide.
NOTE: Certain math operations can attempt to generate output values that exceed
the bounds of a 32-bit integer in various ways. For example, Log2(0) and
Tan(1000) is infinite. In such situations, generally the Math Library will
return a value of MaxInt or MinInt. However, if you are also using the new
Task Control Module, TCM, feature of the KSE, and you have the TCM_DEBUG
mode enabled, the Math Library will also create a tcm.exception and set it
to the value of MATH_OVERFLOW.
-------------------------------------------------------------------------------
Library Functions Table of Contents
-------------------------------------------------------------------------------
The library is organized alphabetically by function name within each of its
subsections. Sections 1.0 to 4.0 contain all the functions you can reference
from your application. Section 5.0 and its subsections are support routines
for the library and generally you will not need to concern yourself with these
routines unless you intend to make custom modifications to the library.
macro 2dArray(#name#,d1,d2)
declare _#name#[d1*d2]
property #name#
function get(x,y) -> result
result := _#name#[d2*x + y]
end function
function set(x,y,val)
_#name#[d2*x + y] := val
end function
end property
end macro { d2dArray }
{ 3dArray(name,d1,d2,d3)
macro 3dArray(#name#,d1,d2,d3)
declare _#name#[d1*d2*d3]
property #name#
function get(x,y,z) -> result
result := _#name#[d2*d3*x + d3*y + z]
end function
function set(x,y,z,val)
_#name#[d2*d3*x + d3*y+ z] := val
end function
end property
end macro { 3dArray }
{ do_post_init(init_rtn)
This macro should be invoked some convenient place within your script's
pgs callback. If you have specified that you want to trigger a post ICB
initialization function by including PI in the SetMathMode option_list,
the pgs callback will be triggered as soon as the ICB exits and the KS
function whose name you pass as the init_rtn parameter will be executed.
To ececute a KN function instead, pass the routine name prefixed by the
call keyword.
If your script doesn't include a pgs callback, you can instead invoke
the macro named on_pgs_do_post(init_rtn) and it will both create the
callback header and generate the do_post_init code for you.
macro do_post_init(init_rtn)
if MODE_FLAGS .and. PI # 0
if post_icb_math = 1
post_icb_math := 0
init_rtn
end if
end if
end macro { do_post_init }
{ This macro will create both a pgs callback header and embed the do_post_init
function within it. If you have specified that you want to trigger a post
ICB initialization (by including PI in the SetMathMode option_list), the
pgs callback will be triggered as soon as the ICB exits the KS function
whose name you pass as the init_rtn parameter will be executed. To ececute
a KN function instead, pass the routine name prefixed by the call keyword.
See also, do_post_init }
macro on_pgs_do_post(init_rtn)
on pgs_changed
do_post_init(init_rtn)
end on
end macro { on_pgs_do_post }
{ PackedArray(name,d1,d2,ft)
The 32 bits of each array word can be grouped as an arbitrary number (2..32) of
fields. Each field can be from 1 to 31 bits wide but of course the total of the
field widths cannot exceed 32 bits. The field layout is specified by the field
template array, ft.
where ax is the array's word index and fx is the desired field index.
As exemplified above, the field template should specify all the field sizes
in bits using the format ft[nf] := (f0,f1,f2, .. fn) where f0 is the bit width
of the leftmost field, f1 is the width of the next contiguous field to the
right, etc.
NOTE: The sum of all the fn widths must be equal or less than 32 bits.
The value stored in any field must be a positive number that will
fit in the number of bits assigned to the field. For example, a
field width of 5 bits can contain values from 0 to 31, or in
general the range of values is: 0 to 2^fn-1 }
macro PackedArray(#name#,d1,d2,ft)
declare _#name#[d1] { declare the actual array }
declare #name#.mask[d2] { bit-mask for field n at position 0 }
declare #name#.shft[d2] { rightmost bit position in word for field n }
MI := 32 { bit position }
MO := 0 { n, compile-time field index }
{ compile-time loop to create shift and mask arrays for each field }
while MO < d2 and (MI - ft[MO]) >= 0
MI := MI - ft[MO]
#name#.shft[MO] := MI { rightmost bit of field n }
#name#.mask[MO] := sh_left(1,ft[MO])-1 { field n mask }
inc(MO)
end while
property #name# { property construct to access array/field }
function get(ax,fx) -> result { one-line get }
result := sh_right(_#name#[ax],#name#.shft[fx]) .and. #name#.mask[fx]
end function
function set(ax,fx,val) { one-line set }
_#name#[ax] := _#name#[ax] .and. .not.
sh_left(#name#.mask[fx],#name#.shft[fx]) ...
.or. sh_left(val .and. #name#.mask[fx],#name#.shft[fx])
end function
end property
end macro { PackedArray }
{----------------------------------------------------------------------------
SetMathMode(option_list)
This macro must always be invoked from your script's ICB in order
to use the Math Library. The option_list is formed from the sum
of the desired option codes. Besides enabling certain special
features, the options_list also specifies which Math routines you
want to run in Fast Mode. Please see the User's Guide for more
detailed information about setting the option_list.
If you don't want to activate the fast-mode option at all, and, you
aren't using any special features, simply use SetMathMode(0) in your
ICB.
macro SetMathMode(option_list)
DefineOptionCodes
declare const MODE_FLAGS := option_list { Mode control bits }
DefineMathConst
DefineMathPars
LoadFiles
if MODE_FLAGS .and. PI # 0 { If PI is in option_list }
pgs_create_key(MATH__PINIT,1)
post_icb_math := 1 { Authorize pgs to execute post_icb_math }
pgs_set_key_val(MATH__PINIT,0,1) { Trigger pgs callback }
end if
if MODE_FLAGS .and. RI # 0 { If RI is in option_list }
randseed := ENGINE_UPTIME { random seed the random generator }
end if
if MODE_FLAGS .and. HX # 0 { Fill HexDgt table if HX option is enabled }
HexDgt[0] := 'A'
HexDgt[1] := 'B'
HexDgt[2] := 'C'
HexDgt[3] := 'D'
HexDgt[4] := 'E'
HexDgt[5] := 'F'
end if
FilesOK
end macro { SetMathMode }
{ The .nka files needed to support the fast-mode versions of the library routines
can easily be created or recreated using a simple Utility Script that invokes
the macro named write_nka. A sample of such a script can be found in the
file named 'NKA_Utility.txt' included with the library. For information on how
to use this script to write the .nka files, please read the header comments of
the script or see the User's Guide. }
{ ------------------------------------------------------------------------------}
{ write_nka
This macro should be invoked in your utility script right after the Math
Library import directive as shown next.
import "KSPMathV450.txt"
write_nka
However, if you want to create nka files for a host script that will import
the library as xxx, you also need to prefix the macro with xxx. For example,
if your host script will import the math library as Math, the utility script
should contain the following two lines of code.
As can be seen from the above examples, only these two lines of code are
needed to compile your Utility Script. }
macro write_nka
on init
{#pragma preserve_names FSin FLog FExp}
make_perfview
message('')
DefineMathConst { Ang90, MaxInt, Muted }
DefineOptionCodes { declare F1, F2, F3, etc }
declare const MODE_FLAGS := 0 { Use standard math mode }
DefineFastArrays { declare all fast-mode arrays }
DefineMathPars { declare KN function parameters }
declare ui_menu NKA_menu { Menu to select the .nka file to write }
add_menu_item(NKA_menu,' Choose NKA',-1)
add_menu_item(NKA_menu,'----------------',0)
add_menu_item(NKA_menu,'Write FLog.nka',1)
add_menu_item(NKA_menu,'Write FExp.nka',2)
add_menu_item(NKA_menu,'Write FSin.nka',3)
add_menu_item(NKA_menu,'',-2)
move_control(NKA_menu,1,1)
NKA_menu := -1
end on
on ui_control(NKA_menu)
WriteNKA(NKA_menu) { write the selected .nka file }
end on
end macro { write_nka }
{ ------------------------------------------------------------------
Cos(ang) First Quadrant Cosine Function
Computes: 10000*Cos(ang)
Fast Mode: F3 - FSin.nka
{ ------------------------------------------------------------------------------
Exp2(X) Binary Exponential (antiLog) Function (base 2)
{ -------------------------------------------------------------------------
Exp10(X) Common AntiLog Function (base 10)
{ -------------------------------------------------------------------------
Log2(X) Binary Logarithm Function (base 2)
{ MulDiv64 calculates result = X*Y/Z where X, Y, and Z are any 32-bit, signed
integers **. The interim product of X*Y is held as a 64-bit value and as long
as the correct result is in the range from -MaxInt <= result <= MaxInt, no
arithmetic overflow will occur. If the result cannot be contained in a 32-bit
integer, the result returned from MulDiv64 will be set to MaxInt with the
approrpiate sign. Optionally, a MATH_OVERFLOW exception will also be generated
if you have the TCM_DEBUG mode enabled.
function Rand(min,max) -> result { X = new random value: min <= X <= max }
randseed := 8088405*randseed + 1
result := (sh_right(randseed,8) .and. 0xFFFFFF) mod (max - min + 1) + min
end function { Rand }
{ ResetRand
This routine reseeds the Rand generator and can be used whenever
a repeatable sequence of 'random' numbers might be required. }
function ResetRand
randseed := 1107155288
end function { ResetRand }
{ -------------------------------------------------------------------------------
Root2(X) Returns the square-root of any positive input integer
{ -------------------------------------------------------------------------------
Root3(X) Returns the cube-root of any input integer
{ ------------------------------------------------------------------
Sin(ang) First Quadrant Sine Function
Computes: 10000*sin(ang)
Fast Mode: F3 - FSin.nka
function SinCos(ang,sin,cos)
MI := ang
call _SinCos
sin := MO
cos := MO2
end function { SinCos }
{ -------------------------------------------------------------------
Tan(ang) First-Quadrant Tangent Function
{ -------------------------------------------------------------------
XCos(ang) Extended-Angle Cosine Function
Computes: 10000*Cos(ang)
Fast Mode: F3 - FSin.nka }
{ -------------------------------------------------------------------
XSin(ang) Extended-Angle Sine Function
ang = Input angle in deci-grads (1000 deci-grads = 90 degrees)
Angle may be any 32-bit signed-integer
Computes: 10000*Sin(ang)
Fast Mode: F3 - FSin.nka }
{ -------------------------------------------------------------------
XSinCos(ang,sin,cos) Extended-Angle Sine/Cosine Function
function XSinCos(ang,sin,cos)
MI := ang
call _XSinCos
sin := MO
cos := MO2
end function { XSinCos }
{ -------------------------------------------------------------------
XTan(ang) Extended-Angle Tangent Function
{ The following four functions each take two input arguments and provide two
output values. The inputs are the crossfade control variable, 0 <= xv <= 1000
and the Morph control variable, 0 <= mv <= 1000. The outputs are the up-curve
volume ratio VR1 and down-curve volume ratio VR2, each scaled by 10000. As xv
swings from 0 to 1000, VR1 swings from 0 to 10000 while VR2 swings from 10000
to 0. However, if you need to control a K4 mdb function or engine-parameter
function, you can use the library format converters VR_to_mdb or epVR to
convert the outputs to the needed control format.
The morphing variable has zero effect when mv = 0 and it fully linearizes the
curves when mv = 1000. Of course, between these two limits, the curves morph.
function EP_XFade(xv,mv,VR1,VR2)
MI := xv
MI2 := mv
call _EPXFade
VR1 := MO
VR2 := MO2
end function { EP_XFade }
function S1_XFade(xv,mv,VR1,VR2)
MI := xv
MI2 := mv
call _S1XFade
VR1 := MO
VR2 := MO2
end function { S1_XFade }
When mv = 0, this variation provides an exponential curve for 0 <= xv <= 500
and a logarithmic curve for 500 <= xv <= 1000
function S2_XFade(xv,mv,VR1,VR2)
MI := xv
MI2 := mv
call _S2XFade
VR1 := MO
VR2 := MO2
end function { S2_XFade }
{ --------------------------------------------------------------------------
epARFreq(mode,F) Engine Parameter Converter for AR filter Frequency
(including LP2, LP4, LP2/4, HP2, HP4, HP2/4, BP2, BP4, BP2/4 )
F = frequency: 8.2 to 35500.0 Hz (scaled by 10)
{ --------------------------------------------------------------------------
epAtkTime(mode,T) Engine Parameter Converter for Envelope Attack Time
Time: 0.00 to 15000.00 msec (scaled by 100)
{ --------------------------------------------------------------------------
epDAFTFreq(mode,F) Engine Parameter Converter for DAFT filter cutoff freq
Frequency: 26.0 to 35500.0 (scaled by 10)
else { ep -> F }
MI := (552*gp+26)/53 + 8022368 { K*ep + 10^6*Lg(Fmin) }
call _Exp2
result := MO
end if
end function { epDAFTFreq }
{ --------------------------------------------------------------------------
epDRTime(mode,t) Engine Parameter Converter for Envelope Decay/Release
Time: 0 to 25000.00 msec (scaled by 100)
{ --------------------------------------------------------------------------
epEqBW(mode,bw) Engine Par Converter for EQ Bandwidth
bw: 0.30..3.00 octaves (scaled by 100)
{ --------------------------------------------------------------------------
epEqFreq(mode,F) Engine Par Converter for EQ frequency (1,2, or 3 band)
F = frequency: 20.0 to 20000.0 Hz (scaled by 10)
{ --------------------------------------------------------------------------
epEqGain(mode,G) Engine Par Converter for EQ Bandwidth
G: -18.0 to +18.0 db (scaled by 10)
{ --------------------------------------------------------------------------
epHBFreq(mode,F) Engine Parameter Converter for Legacy HP/BP filters
including HP1, HP4, BP2, BP4, and BR4
Frequency: 36.1 to 18100.0 Hz (scaled by 10)
{ --------------------------------------------------------------------------
epHP2Freq(mode,F) Engine Par Converter for Legacy HP2 filter Freqency
F: 37.3 to 18700.0 Hz (scaled by 10)
{ --------------------------------------------------------------------------
epLPFreq(mode,F) Engine Parameter Converter for Legacy LP1, LP2, LP4, LP6
F = frequency: 43.6 to 21800.0 Hz (scaled by 10)
{ -----------------------------------------------------------------------
epProFreq(mode,F) Engine Par Converter for LP Pro53 and Legacy Ladder
F = frequency: 26.0 to 8400.0 Hz (scaled by 10)
{ --------------------------------------------------------------------------
epSGEqBW(mode,Q) Engine Par Converter for Solid G-EQ Bandwidth
Q: 0.70..2.50 (scaled by 100)
{ --------------------------------------------------------------------------
epSGEqFreq(mode,band,F) Engine Par Converter for Solid G-EQ Frequency
{ --------------------------------------------------------------------------
epSGEqGain(mode,G) Engine Par Converter for Solid G-EQ Gain
G: -20.0 to +20.0 db (scaled by 10)
{ --------------------------------------------------------------------------
epSpeed(mode,S) Engine Parameter Converter for Speed (time/beat machines)
S: 0.0 to 800.0% (scaled by 10)
{ --------------------------------------------------------------------------
epSVFreq(mode,F) Engine Parameter Converter for non-multi SV filters
(including: Ladder Peak, Ladder Notch, Ladder BP2/4,
Ladder HP1/2/3/4, Ladder LP1/2/3/4, SV Notch4,
SV BP2/4, SV HP1/2/4, SV LP1/2/4)
{ --------------------------------------------------------------------------
epSVMFreq(mode,F) Engine Parameter Converter for SV Multi-filters
(for SV Par LP/HP, SV Par BP/BP, and SV Ser LP/HP)
F = frequency: 2.6 to 84400.0 Hz (scaled by 10)
{ --------------------------------------------------------------------------
epTune(mode,T) Engine Parameter Converter for Tuning
T: -3600 to 3600 cents
{ --------------------------------------------------------------------------
epV3x2Freq(mode,F) Engine Parameter Converter for Multi Versatile 3x2 filter
{ --------------------------------------------------------------------------
epVolume(mode,vol) Engine Parameter Converter for Volume
Volume: -346768 to 12000 mdb ( using 18db/octave curve )
{ --------------------------------------------------------------------------
epVR(mode,vr) Engine Parameter Converter for Volume Ratio
vr: 0.0000 to 4.0000 (scaled by 10000)
where 1.0000 = 0 db
{ --------------------------------------------------------------------------
ModInt_to_ep(type,P) Modulation Intensity Linearizer
{-------------------------------------------------------------------------------
NOTE: This routine performs the same function as the legacy routine Get_db
except that Get_db limited the input range to 0.0 <= vr <= 1.0 whereas
VR_to_mdb allows the wider input range of 0.0 < V/V0 <= 4.0.}
function BuildAngleTable
declare global const TrigBits := 15 { Precision in bits (#of iterations ) }
declare global const AngScale := 1000000 { Input Angle scaling factor }
declare global AngTbl[TrigBits+1] := { Angle Table (dg scaled by 10^6) } ...
{ AngTbl[n] = arctan(2^-n) and AngTbl[0] is not used } (500000000, ...
295167235, 155958261, 79166848, 39737049, 19887896, ...
9946375, 4973491, 2486783, 1243396, 621699, ...
310849, 155425, 77712, 38856, 19428)
end function { BuildAngleTable }
{ Build.FLog Since Log2(0) is not defined, only Log2(1) and up are stored in
the FLog array. Thus Log2(1) is stored at FLog[0], Log2(2) is stored
at FLog[1], etc. The FLog array is accessed to retrieve logs from
Log2(1) to Log2(32767) with FLog[0] to FLog[32766]. For convenience
the last array slot, FLog[32767] is used to store Lg(32767.977+).
This additional value allows the algorithmic code for rounding to
be simplified. If Log2(32768) = 15000000 would be used, MaxInt
would cause Log2 to output 31000000 which can cause overflow
problems when XLog10 converts from base 2 }
{ ---------------------------------------------------------------------------
BuildLogTable Data Function to declare the Logs table used by the
S.Lg and _ALg.Core functions. Currently the Logs
array elements are scaled by 10^9.
NOTE: The Logs array elements contain the value Logs[n] = Lg(1+2^-n) for each
n from 1 to LogBits, Logs[0] is not used. Both _XLg.Core and _ALg.Core
use a rounding 'constant' named LSB2 which is set to Logs[LogBits]/2.
If LogBits is changed, the value of this rounding constant must also be
changed. Therefore, it is defined here to keep it with the Logs table. }
function BuildLogTable
declare global const LogBits := 20 { Precision bits (or #of loop iterations) }
declare global Logs[LogBits+1] := { Table Lg(1+2^-n), 0 < Logs[n] < 1 } ...
(1000000000, 584962501, 321928095, 169925001, 87462841, 44394119, ...
22367813, 11227256, 5624549, 2815016, 1408195, 704269, 352178, ...
176099, 88052, 44028, 22014, 11006, 5504, 2751, 1376)
declare global const LSB2 := 688 { Rounding Bias, Logs[LogBits]/2 }
end function { BuildLogTable }
function DefineOptionCodes { File code constants, etc for each file/function type
}
declare global const PI := 1 { Trigger pgs to execute post-ICB init routine }
declare global const F1 := 2 { FLog.nka file code }
declare global const F2 := 4 { FExp.nka file code }
declare global const F3 := 8 { FSin.nka file code }
declare global const RI := 16 { Initialize randseed randomly }
declare global const HX := 32 { Application uses CVHex }
end function { DefineFileCodes }
function FilesOK
if MODE_FLAGS .and. (F1+F2+F3) # 0
if NKA_Status # 0
message('WARNING: NKA File(s) Missing')
end if
end if
end function { FilesOK }
{--------------------------------------------------------------------------------
Fast/Standard Mode Switches
---------------------------------------------------------------------------------}
{ The following 4 routines are conditional compilation mode switches
for the 4 core routines. With the KSE Optimize code option enabled,
these routines each reduce to a KS function invokation. For example,
_MALg becomes either F.ALg or S.ALg depending on the MODE_FLAGS bit
pattern (which the scripter sets with SetMathMode's option_list). }
function qsign(x,y,z) -> result { sign of quotient for x*y/z, xt=0.3 usec }
result := (sh_right(x,31)+sh_right(y,31)+sh_right(z,31)) mod 2 .or. 1
end function { qsign }
This routine uses the FLog array to obtain values for Log2,
scaled by 1000000. When 1 <= X <= 0x7FFF, the result can be
obtained directly from FLog[X-1]. For larger X, this routine
first reduces the input argument X until it lies within the
normalized range 0x4000 <= X <= 0x7FFF. The reductions are
performed in even powers of two and the number of such
'right shifts' are tallied as C' (in MO2). The output is
thus delivered in two pieces, M' (in MO) is a mantissa that
may be larger than 1000000 while C' (in MO2) contains the
corresponding additional characteristic. The rounding part
of normalization may result in X = 0x8000, so to avoid an
additional reduction step, the last FLog array slot is used
to cover this situation. See the Technical Guide Addendum
for details.
C := MI/1000000 { Characteristic of Z }
m := MI mod 1000000 { Mantissa of Z, scaled by 10^6 }
MO := Bit30 { SX = 1.0 scaled by 2^30 }
if m # 0 { Skip mantissa iteration loop for even powers of two }
m := m*1000 + LSB2 { Rescale m to match table (10^9) and bias by LSB/2 }
n := 1
while n <= LogBits { Execute Cordic Loop to drive m -> 0.0 and SX -> 2^m }
if m >= Logs[n]
m := m - Logs[n]
MO := MO + sh_right(MO,n) { Accumulated scaled product for SX = 2^m }
end if
inc(n)
end while { SX now = 2^m * 2^30 }
end if
{ If C = 30, X = SX; else X = SX*2^(C-30) }
if C < 30 { Right shift SX by (30-C) with rounding }
MO := sh_right(sh_right(MO,29-C)+1,1) .and. 0x7FFFFFFF
end if
end function { S.ALg }
This routine uses a CORDIC-Like algorithm that utilizes the same Logs
table as that used by the core exponential support routine S.ALg. }
This routine uses the binary CORDIC algorithm and a small lookup
table to compute both the Sine and Cosine in one pass. }
NOTE: All functions in this library whose names begin with an underscore
are intended to be 'called' as KN functions as opposed to being inline
expanded as KS functions. Therefore, all the following functions pass
values in and out via the global Math Library parameter set. }
{ Returns the Cosine over 180 degrees of input angle (0 to 2000 dg) }
function _Cos180 {MI=ang, MO2=Cos(ang) MI altered}
if MI > 1000 { in quadrant 2, use supplement }
MI := 2000 - MI
call _SinCos
MO2 := -MO2 { cosine is negative in Q2 }
else { in quadrant 1 }
call _SinCos
end if
end function { _Cos180 }
function _NLZ { MI, MO leading zeros 1..31, not valid for MI <= 0 }
select MI { for MI <= 0, routine returns 1 }
case 1 to 0x7F
MO := 25
case 0x80 to 0xFFF
MO := 19
case 0x1000 to 0x3FFFF
MO := 13
case 0x40000 to 0xFFFFFF
MO := 7
else
MO := 1
end select
select sh_left(MI,MO-1)
case 0x01000000 to 0x02000000-1
MO := MO + 6
case 0x02000000 to 0x04000000-1
MO := MO + 5
case 0x04000000 to 0x08000000-1
MO := MO + 4
case 0x08000000 to 0x10000000-1
MO := MO + 3
case 0x10000000 to 0x20000000-1
MO := MO + 2
case 0x20000000 to 0x40000000-1
MO := MO + 1
end select
end function { _NLZ }
function _ReduceAngle { In: MI = ang, Out: MI = reduced ang, 0..+90, MI2 = Quad - 1
}
declare const Ang360 := 4*Ang90
declare const Ang180 := 2*Ang90
MI := ((MI mod Ang360) + Ang360) mod Ang360 { 0..+360 }
MI2 := (MI - 1)/Ang90 { quadrant - 1 = 0..3 }
MI := MI mod Ang180 { 0..+180 }
MI := MI + ((MI-1)/Ang90)*(Ang180 - 2*MI) { 0..+90 }
end function