1838 lines
58 KiB
C
1838 lines
58 KiB
C
/**
|
||
*
|
||
* @file FR_math.c - c implementation file for basic fixed
|
||
* radix math routines
|
||
*
|
||
* @copy Copyright (C) <2001-2026> <M. A. Chatterjee>
|
||
* @author M A Chatterjee <deftio [at] deftio [dot] com>
|
||
*
|
||
* This file contains integer math settable fixed point radix math routines for
|
||
* use on systems in which floating point is not desired or unavailable.
|
||
*
|
||
* This software is provided 'as-is', without any express or implied
|
||
* warranty. In no event will the authors be held liable for any damages
|
||
* arising from the use of this software.
|
||
*
|
||
* Permission is granted to anyone to use this software for any purpose,
|
||
* including commercial applications, and to alter it and redistribute it
|
||
* freely, subject to the following restrictions:
|
||
*
|
||
* 1. The origin of this software must not be misrepresented; you must not
|
||
* claim that you wrote the original software. If you use this software
|
||
* in a product, please place an acknowledgment in the product documentation.
|
||
*
|
||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||
* misrepresented as being the original software.
|
||
*
|
||
* 3. This notice may not be removed or altered from any source
|
||
* distribution.
|
||
*
|
||
*/
|
||
|
||
#include "FR_math.h"
|
||
|
||
#ifndef FR_NO_STDINT
|
||
#include <stdint.h>
|
||
#endif
|
||
|
||
/*=======================================================
|
||
* Trig lookup tables (inlined — no separate header needed)
|
||
*
|
||
* Sine quadrant table: 129 entries covering [0, pi/2] in u0.15 format.
|
||
* Tangent octant table: 65 entries covering [0, pi/4] in u0.15 format.
|
||
* Generated by tools/coef-gen.py — do not hand-edit.
|
||
*/
|
||
|
||
#define FR_TRIG_TABLE_BITS (7)
|
||
#define FR_TRIG_TABLE_SIZE ((1 << FR_TRIG_TABLE_BITS) + 1)
|
||
|
||
#define FR_TRIG_FRAC_BITS (14 - FR_TRIG_TABLE_BITS)
|
||
#define FR_TRIG_FRAC_MAX (1 << FR_TRIG_FRAC_BITS)
|
||
#define FR_TRIG_FRAC_MASK (FR_TRIG_FRAC_MAX - 1)
|
||
#define FR_TRIG_FRAC_HALF (FR_TRIG_FRAC_MAX >> 1)
|
||
#define FR_TRIG_QUADRANT (1 << 14)
|
||
|
||
static const unsigned short gFR_SIN_TAB_Q[FR_TRIG_TABLE_SIZE] = {
|
||
0, 402, 804, 1206, 1608, 2009, 2411, 2811,
|
||
3212, 3612, 4011, 4410, 4808, 5205, 5602, 5998,
|
||
6393, 6787, 7180, 7571, 7962, 8351, 8740, 9127,
|
||
9512, 9896, 10279, 10660, 11039, 11417, 11793, 12167,
|
||
12540, 12910, 13279, 13646, 14010, 14373, 14733, 15091,
|
||
15447, 15800, 16151, 16500, 16846, 17190, 17531, 17869,
|
||
18205, 18538, 18868, 19195, 19520, 19841, 20160, 20475,
|
||
20788, 21097, 21403, 21706, 22006, 22302, 22595, 22884,
|
||
23170, 23453, 23732, 24008, 24279, 24548, 24812, 25073,
|
||
25330, 25583, 25833, 26078, 26320, 26557, 26791, 27020,
|
||
27246, 27467, 27684, 27897, 28106, 28311, 28511, 28707,
|
||
28899, 29086, 29269, 29448, 29622, 29792, 29957, 30118,
|
||
30274, 30425, 30572, 30715, 30853, 30986, 31114, 31238,
|
||
31357, 31471, 31581, 31686, 31786, 31881, 31972, 32058,
|
||
32138, 32214, 32286, 32352, 32413, 32470, 32522, 32568,
|
||
32610, 32647, 32679, 32706, 32729, 32746, 32758, 32766,
|
||
32768
|
||
};
|
||
|
||
#define FR_TAN_TABLE_BITS (6)
|
||
#define FR_TAN_TABLE_SIZE ((1 << FR_TAN_TABLE_BITS) + 1)
|
||
#define FR_TAN_FRAC_BITS (13 - FR_TAN_TABLE_BITS)
|
||
#define FR_TAN_FRAC_MAX (1 << FR_TAN_FRAC_BITS)
|
||
#define FR_TAN_FRAC_MASK (FR_TAN_FRAC_MAX - 1)
|
||
#define FR_TAN_FRAC_HALF (FR_TAN_FRAC_MAX >> 1)
|
||
#define FR_TAN_OCTANT (1 << 13)
|
||
|
||
static const unsigned short gFR_TAN_TAB_O[FR_TAN_TABLE_SIZE] = {
|
||
0, 402, 804, 1207, 1610, 2013, 2417, 2822,
|
||
3227, 3634, 4042, 4450, 4861, 5272, 5686, 6101,
|
||
6518, 6937, 7358, 7782, 8208, 8637, 9068, 9503,
|
||
9940, 10381, 10825, 11273, 11725, 12180, 12640, 13104,
|
||
13573, 14046, 14525, 15009, 15498, 15993, 16494, 17001,
|
||
17515, 18035, 18563, 19098, 19640, 20191, 20750, 21318,
|
||
21895, 22481, 23078, 23685, 24302, 24931, 25572, 26226,
|
||
26892, 27572, 28266, 28975, 29699, 30440, 31198, 31973,
|
||
32768
|
||
};
|
||
|
||
/*=======================================================
|
||
* Full-precision radian/degree → BAM conversion helpers
|
||
*
|
||
* rad_to_bam_full(r) returns a full s32 BAM value where:
|
||
* upper 16 bits = integer BAM (the u16 table index)
|
||
* lower 16 bits = sub-BAM fractional part
|
||
* Input r must already be normalized to radix 16 and reduced to [-pi, pi].
|
||
*
|
||
* The shift terms match FR_RAD2BAM (10 terms, ~21-bit accuracy) but are
|
||
* reordered so intermediate sums stay within s32 for |r| <= pi at r16.
|
||
*/
|
||
static s32 rad_to_bam_full(s32 r)
|
||
{
|
||
/* 10 terms: 65536/(2*pi) ≈ 10430.37835...
|
||
* 2^13 + 2^11 + 2^7 + 2^6 - 2 + 0.5 - 0.125 + 2^-8 - 2^-11 - 2^-14
|
||
* = 10430.378357 (~21-bit accuracy)
|
||
* Terms reordered: interleave negatives early to keep all intermediate
|
||
* sums within s32 for |r| <= pi at r16 (max result ≈ 2^31 - 4K). */
|
||
return (r<<13)-(r<<1)+(r<<11)-(r>>3)+(r<<7)+(r<<6)+(r>>1)+(r>>8)-(r>>11)-(r>>14);
|
||
}
|
||
|
||
#ifndef FR_LEAN
|
||
/* deg_to_bam_full(d) — same idea for degrees.
|
||
* Input d must already be normalized to radix 16 and reduced to [-90, 90).
|
||
* Returns full s32 BAM (upper 16 = integer BAM, lower 16 = sub-BAM).
|
||
* 7 terms, ~18-bit accuracy matching FR_DEG2BAM. */
|
||
static s32 deg_to_bam_full(s32 d)
|
||
{
|
||
return (d<<7)+(d<<6)-(d<<3)-(d<<1)+(d>>5)+(d>>6)-(d>>9);
|
||
}
|
||
#endif
|
||
|
||
/* Normalize a fixed-radix value to radix 16. */
|
||
static s32 normalize_to_r16(s32 val, u16 radix)
|
||
{
|
||
return (radix > 16) ? (val >> (radix - 16))
|
||
: (radix < 16) ? (val << (16 - radix))
|
||
: val;
|
||
}
|
||
|
||
/* Reduce non-negative radian (at r16) to [0, 2*pi). */
|
||
static s32 reduce_to_2pi(s32 r)
|
||
{
|
||
const s32 two_pi = FR_TWO_PI(16); /* 411775 */
|
||
if (r > (two_pi << 1))
|
||
r -= (r / two_pi) * two_pi;
|
||
else if (r > two_pi)
|
||
r -= two_pi;
|
||
return r;
|
||
}
|
||
|
||
|
||
/* rad_r16_to_bam — convert radian (at r16) in [0, 2π) to u16 BAM.
|
||
* Uses quadrant decomposition to keep rad_to_bam_full in its safe
|
||
* [-π/2, π/2) range, mirroring the approach in fr_deg_to_bam. */
|
||
static u16 rad_r16_to_bam(s32 r)
|
||
{
|
||
const s32 half_pi = FR_HALF_PI(16); /* 102944 */
|
||
const s32 three_half_pi = FR_THREE_HALF_PI(16); /* 308831 */
|
||
const s32 pi = FR_PI(16); /* 205887 */
|
||
const s32 two_pi = FR_TWO_PI(16); /* 411775 */
|
||
|
||
u16 offset = 0;
|
||
if (r >= half_pi && r < three_half_pi) {
|
||
r -= pi;
|
||
offset = 0x8000u;
|
||
} else if (r >= three_half_pi) {
|
||
r -= two_pi;
|
||
/* r is now in [-π/2, 0), no offset needed (u16 wraps naturally) */
|
||
}
|
||
return (u16)(offset + (u16)((rad_to_bam_full(r) + (1 << 15)) >> 16));
|
||
}
|
||
|
||
/* (rad_r16_to_bam32 removed — sub-BAM interpolation approach abandoned) */
|
||
|
||
/* fr_rad_to_bam — overflow-safe radian to u16 BAM conversion.
|
||
* Normalizes to r16, reduces to [0, 2π), uses quadrant decomposition. */
|
||
u16 fr_rad_to_bam(s32 rad, u16 radix)
|
||
{
|
||
s32 r = normalize_to_r16(rad, radix);
|
||
/* Normalize to [0, 2π) */
|
||
if (r < 0) {
|
||
r += ((-r) / FR_TWO_PI(16)) * FR_TWO_PI(16);
|
||
if (r < 0) r += FR_TWO_PI(16);
|
||
}
|
||
r = reduce_to_2pi(r);
|
||
return rad_r16_to_bam(r);
|
||
}
|
||
|
||
#ifndef FR_LEAN
|
||
/* fr_deg_to_bam — overflow-safe degree to u16 BAM conversion.
|
||
* Normalizes to r16, reduces to [-90, 90) with quadrant offset. */
|
||
u16 fr_deg_to_bam(s32 deg, u16 radix)
|
||
{
|
||
s32 d = normalize_to_r16(deg, radix);
|
||
|
||
/* Reduce to [-180, 180) */
|
||
if (d >= FR_D360_R16 || d < -FR_D360_R16) {
|
||
s32 n = d / FR_D360_R16;
|
||
d -= n * FR_D360_R16;
|
||
}
|
||
if (d >= FR_D180_R16) d -= FR_D360_R16;
|
||
if (d < -FR_D180_R16) d += FR_D360_R16;
|
||
|
||
/* Reduce to [-90, 90) with BAM quadrant offset */
|
||
u16 offset = 0;
|
||
if (d >= FR_D90_R16) { d -= FR_D180_R16; offset = 32768; }
|
||
else if (d < -FR_D90_R16) { d += FR_D180_R16; offset = 32768; }
|
||
|
||
return (u16)(offset + (u16)((deg_to_bam_full(d) + (1 << 15)) >> 16));
|
||
}
|
||
#endif
|
||
|
||
/*=======================================================
|
||
* BAM-native trig: fr_sin_bam, fr_cos_bam, fr_cos, fr_sin, fr_tan
|
||
*
|
||
* Internal model: every angle is reduced to a u16 BAM value. The top 2 bits
|
||
* select the quadrant, the bottom 14 bits are the in-quadrant position. Odd
|
||
* quadrants (1, 3) reverse the in-quadrant index so the table is always read
|
||
* in the same direction.
|
||
*
|
||
* The table is a 129-entry SINE quadrant (ascending: 0 at index 0, 32768 at
|
||
* index 128). After mirroring, small full_pos → small output (near zero),
|
||
* which enables a cheap small-angle approximation: sin(θ) ≈ θ for angles
|
||
* below one table step (~0.7°). This eliminates table quantization error
|
||
* in the region where it matters most.
|
||
*
|
||
* Sign rule: quadrants 2 and 3 negate the result.
|
||
* Mirror rule: quadrants 1 and 3 flip the in-quadrant position.
|
||
*/
|
||
s32 fr_sin_bam(u16 bam)
|
||
{
|
||
u32 q = ((u32)bam >> 14) & 0x3; /* top 2 bits = quadrant */
|
||
u32 inq = (u32)bam & (FR_TRIG_QUADRANT - 1); /* bottom 14 bits */
|
||
|
||
/* Exact cardinal angles */
|
||
if (inq == 0) {
|
||
if (q == 0 || q == 2) return 0; /* 0° or 180° → 0 */
|
||
if (q == 1) return FR_TRIG_ONE; /* 90° → 1.0 */
|
||
return -FR_TRIG_ONE; /* 270° → -1.0 */
|
||
}
|
||
|
||
/* Odd quadrants mirror: read table from the far end */
|
||
if (q == 1 || q == 3)
|
||
inq = FR_TRIG_QUADRANT - inq;
|
||
|
||
s32 v;
|
||
|
||
/* Small-angle approximation: sin(θ) ≈ θ for inq < 128 (one table step).
|
||
* θ_rad = inq * (π/2) / 16384. Output = θ * 65536 = inq * FR_kQ2RAD / 16384.
|
||
* Max inq=127: 127 * 102944 / 16384 = 798. Error: θ³/6 < 3e-7 << 1 LSB. */
|
||
if (inq < FR_TRIG_FRAC_MAX) {
|
||
v = (s32)(((u32)inq * 102944u + 8192u) >> 14);
|
||
} else {
|
||
/* Table lookup with 7-bit interpolation fraction */
|
||
u32 idx = inq >> FR_TRIG_FRAC_BITS;
|
||
u32 frac = inq & FR_TRIG_FRAC_MASK;
|
||
s32 lo = (s32)gFR_SIN_TAB_Q[idx];
|
||
s32 hi = (s32)gFR_SIN_TAB_Q[idx + 1];
|
||
v = lo + (((hi - lo) * (s32)frac + FR_TRIG_FRAC_HALF) >> FR_TRIG_FRAC_BITS);
|
||
v <<= 1; /* u0.15 → s15.16 */
|
||
}
|
||
|
||
return (q >= 2) ? -v : v;
|
||
}
|
||
|
||
s32 fr_cos_bam(u16 bam)
|
||
{
|
||
/* cos(x) = sin(x + pi/2) = sin(bam + 16384). u16 wraparound is free. */
|
||
return fr_sin_bam((u16)(bam + FR_BAM_QUADRANT));
|
||
}
|
||
|
||
s32 fr_cos(s32 rad, u16 radix)
|
||
{
|
||
if (rad == 0) return FR_TRIG_ONE;
|
||
s32 r = normalize_to_r16(rad, radix);
|
||
if (r < 0) r = -r;
|
||
r = reduce_to_2pi(r);
|
||
/* Near π/2 or 3π/2 (cos=0 crossings): cos(π/2+δ) = -sin(δ) ≈ -δ,
|
||
* cos(3π/2+δ) = sin(δ) ≈ δ. */
|
||
s32 delta = r - FR_HALF_PI(16);
|
||
if (delta >= -256 && delta <= 256)
|
||
return -delta;
|
||
delta = r - FR_THREE_HALF_PI(16);
|
||
if (delta >= -256 && delta <= 256)
|
||
return delta;
|
||
return fr_cos_bam(fr_rad_to_bam(rad, radix));
|
||
}
|
||
|
||
s32 fr_sin(s32 rad, u16 radix)
|
||
{
|
||
if (rad == 0) return 0;
|
||
s32 r = normalize_to_r16(rad, radix);
|
||
s32 sign = 1;
|
||
if (r < 0) { r = -r; sign = -1; }
|
||
r = reduce_to_2pi(r);
|
||
/* Near 0 after reduction: sin(δ) ≈ δ */
|
||
if (r < 256) {
|
||
s32 v = r;
|
||
return (sign < 0) ? -v : v;
|
||
}
|
||
/* Near π: sin(π + δ) = -sin(δ) ≈ -δ */
|
||
s32 delta = r - FR_PI(16);
|
||
if (delta >= -256 && delta <= 256) {
|
||
s32 v = -delta;
|
||
return (sign < 0) ? -v : v;
|
||
}
|
||
/* Near 2π: sin(2π - δ) = -sin(δ) ≈ -δ, but δ = 2π - r */
|
||
delta = FR_TWO_PI(16) - r;
|
||
if (delta >= 0 && delta < 256) {
|
||
s32 v = -delta;
|
||
return (sign < 0) ? -v : v;
|
||
}
|
||
/* Main path: reduce to [-π, π], convert to u16 BAM, table lookup */
|
||
if (r > FR_PI(16)) r -= FR_TWO_PI(16);
|
||
u16 bam = (u16)((rad_to_bam_full(r) + (1 << 15)) >> 16);
|
||
s32 v = fr_sin_bam(bam);
|
||
return (sign < 0) ? -v : v;
|
||
}
|
||
|
||
#ifndef FR_LEAN
|
||
/*=======================================================
|
||
* BAM-native tangent: fr_tan_bam
|
||
*
|
||
* Uses a 65-entry octant table (gFR_TAN_TAB_O) for the first octant
|
||
* [0, 45°] and the reciprocal identity tan(x) = 1/tan(90°-x) for the
|
||
* second octant (45°, 90°). Result is s15.16 with saturation at the
|
||
* poles.
|
||
*
|
||
* No 64-bit intermediates. One 32-bit division only in the >45° path.
|
||
*/
|
||
s32 fr_tan_bam(u16 bam)
|
||
{
|
||
u32 q = ((u32)bam >> 14) & 0x3; /* quadrant (top 2 bits) */
|
||
u32 inq = (u32)bam & 0x3FFFu; /* in-quadrant (14 bits) */
|
||
s32 sign = 1;
|
||
u32 idx, frac;
|
||
s32 lo, hi, raw;
|
||
|
||
/* Exact zeros: bam lands exactly on 0° or 180° */
|
||
if (inq == 0 && (q == 0 || q == 2))
|
||
return 0;
|
||
|
||
/* Poles: bam lands exactly on 90° or 270° */
|
||
if (inq == 0 && (q == 1 || q == 3))
|
||
return (q == 1) ? FR_TRIG_MAXVAL : -FR_TRIG_MAXVAL;
|
||
|
||
/* Q1 (90°..180°) and Q3 (270°..360°): reflect and negate */
|
||
if (q == 1 || q == 3) {
|
||
inq = 0x4000u - inq;
|
||
sign = -1;
|
||
}
|
||
|
||
/* Now inq is in (0, 0x4000) = (0°, 90°) exclusive.
|
||
* Split into first octant [0, 45°) and second octant [45°, 90°). */
|
||
if (inq < FR_TAN_OCTANT) {
|
||
/* First octant: direct table lookup + lerp.
|
||
* inq is 13 bits; top FR_TAN_TABLE_BITS index the table,
|
||
* bottom FR_TAN_FRAC_BITS drive interpolation. */
|
||
idx = inq >> FR_TAN_FRAC_BITS;
|
||
frac = inq & FR_TAN_FRAC_MASK;
|
||
lo = (s32)gFR_TAN_TAB_O[idx];
|
||
hi = (s32)gFR_TAN_TAB_O[idx + 1];
|
||
raw = lo + (((hi - lo) * (s32)frac + FR_TAN_FRAC_HALF) >> FR_TAN_FRAC_BITS);
|
||
|
||
if (raw < 0x40) {
|
||
/* Near zero: redo interpolation with 4 extra bits of
|
||
* precision to reduce rounding error when result is small. */
|
||
s32 lo4 = (s32)gFR_TAN_TAB_O[idx] << 4;
|
||
s32 hi4 = (s32)gFR_TAN_TAB_O[idx + 1] << 4;
|
||
raw = lo4 + (((hi4 - lo4) * (s32)frac + FR_TAN_FRAC_HALF) >> FR_TAN_FRAC_BITS);
|
||
raw = (raw + 4) >> 3; /* u0.19 → s15.16 with rounding */
|
||
} else {
|
||
raw <<= 1; /* u0.15 → s15.16 */
|
||
}
|
||
} else {
|
||
/* Second octant: tan(x) = 1 / tan(90° - x).
|
||
* complement is in (0, 0x2000] = (0°, 45°]. */
|
||
u32 comp = 0x4000u - inq;
|
||
|
||
/* Look up tan(complement) from the table */
|
||
idx = comp >> FR_TAN_FRAC_BITS;
|
||
frac = comp & FR_TAN_FRAC_MASK;
|
||
lo = (s32)gFR_TAN_TAB_O[idx];
|
||
hi = (s32)gFR_TAN_TAB_O[idx + 1];
|
||
raw = lo + (((hi - lo) * (s32)frac + FR_TAN_FRAC_HALF) >> FR_TAN_FRAC_BITS);
|
||
|
||
if (raw < 0x40) {
|
||
/* Near pole: redo interpolation with 4 extra bits of
|
||
* precision. The reciprocal amplifies small interpolation
|
||
* errors, so extra precision significantly helps here.
|
||
* Result: (2^31 / raw_hp) << 4 = 2^35 / raw_hp. */
|
||
s32 lo4 = (s32)gFR_TAN_TAB_O[idx] << 4;
|
||
s32 hi4 = (s32)gFR_TAN_TAB_O[idx + 1] << 4;
|
||
s32 raw_hp = lo4 + (((hi4 - lo4) * (s32)frac + FR_TAN_FRAC_HALF) >> FR_TAN_FRAC_BITS);
|
||
if (raw_hp < 32) {
|
||
raw = FR_TRIG_MAXVAL;
|
||
} else {
|
||
raw = (s32)((0x80000000u / (u32)raw_hp) << 4);
|
||
}
|
||
} else {
|
||
raw = (s32)(0x80000000u / (u32)raw);
|
||
}
|
||
}
|
||
|
||
return (sign < 0) ? -raw : raw;
|
||
}
|
||
#endif /* FR_LEAN */
|
||
|
||
/* fr_tan — radian-input tangent.
|
||
*
|
||
* Normalize to [0, 2π], extract quadrant sign, convert rad→u16 BAM,
|
||
* then do direct octant table lookup + interpolation inline.
|
||
* Small-angle bypass at zero crossings: tan(x) ≈ x.
|
||
* Near poles: use radian distance directly (cot(δ) ≈ 1/δ) to avoid
|
||
* BAM quantization error amplified by the reciprocal. */
|
||
s32 fr_tan(s32 rad, u16 radix)
|
||
{
|
||
if (rad == 0) return 0;
|
||
s32 r = normalize_to_r16(rad, radix);
|
||
|
||
/* tan(-x) = -tan(x): extract sign, work with |r| */
|
||
s32 sign = 1;
|
||
if (r < 0) { r = -r; sign = -1; }
|
||
r = reduce_to_2pi(r);
|
||
|
||
/* Small-angle bypass at zero crossings: tan(δ) ≈ δ */
|
||
if (r < 256)
|
||
return (sign < 0) ? -r : r;
|
||
{
|
||
s32 delta = r - FR_PI(16);
|
||
if (delta >= -256 && delta <= 256)
|
||
return (sign < 0) ? -delta : delta;
|
||
}
|
||
{
|
||
s32 delta = FR_TWO_PI(16) - r;
|
||
if (delta >= 0 && delta < 256)
|
||
return (sign < 0) ? delta : -delta;
|
||
}
|
||
|
||
/* Near-pole bypass: within POLE_THRESH r16 of π/2 or 3π/2,
|
||
* use cot(δ) ≈ 1/δ from the radian distance directly.
|
||
* Compute δ at r24 using precise pole constants (8× less rounding
|
||
* error than the r16 FR_HALF_PI/FR_THREE_HALF_PI constants).
|
||
* At δ=2048 r16 (1.79°), 1/δ error is ~0.03%. */
|
||
{
|
||
const s32 pole_thresh = 2048; /* r16 units (~1.79°) */
|
||
/* Precise pole positions at r24:
|
||
* π/2 × 2^24 = 26353589.76 → 26353590
|
||
* 3π/2 × 2^24 = 79060769.28 → 79060769 */
|
||
const s32 half_pi_r24 = 26353590;
|
||
const s32 three_half_pi_r24 = 79060769;
|
||
|
||
s32 d1 = r - FR_HALF_PI(16); /* coarse check at r16 */
|
||
s32 d2 = r - FR_THREE_HALF_PI(16);
|
||
s32 pole_delta_r24 = 0;
|
||
|
||
if (d1 >= -pole_thresh && d1 <= pole_thresh) {
|
||
s32 r24 = r << 8;
|
||
s32 dd = r24 - half_pi_r24;
|
||
pole_delta_r24 = (dd < 0) ? -dd : dd;
|
||
} else if (d2 >= -pole_thresh && d2 <= pole_thresh) {
|
||
s32 r24 = r << 8;
|
||
s32 dd = r24 - three_half_pi_r24;
|
||
pole_delta_r24 = (dd < 0) ? -dd : dd;
|
||
}
|
||
|
||
if (pole_delta_r24 > 0) {
|
||
/* Determine sign from radian quadrant */
|
||
s32 pole_sign;
|
||
if (r < FR_HALF_PI(16))
|
||
pole_sign = 1; /* before π/2: → +∞ */
|
||
else if (r < FR_PI(16))
|
||
pole_sign = -1; /* past π/2: → -∞ */
|
||
else if (r <= FR_THREE_HALF_PI(16))
|
||
pole_sign = 1; /* before 3π/2: → +∞ */
|
||
else
|
||
pole_sign = -1; /* past 3π/2: → -∞ */
|
||
|
||
s32 raw;
|
||
if (pole_delta_r24 < 512) {
|
||
raw = FR_TRIG_MAXVAL; /* δ < 2 at r16 → saturate */
|
||
} else {
|
||
/* cot(δ) ≈ 1/δ. In s15.16: (2^40) / δ_r24 */
|
||
raw = (s32)((1ULL << 40) / (u32)pole_delta_r24);
|
||
if (raw > FR_TRIG_MAXVAL) raw = FR_TRIG_MAXVAL;
|
||
}
|
||
s32 v = (pole_sign < 0) ? -raw : raw;
|
||
return (sign < 0) ? -v : v;
|
||
}
|
||
}
|
||
|
||
/* Convert radian to u16 BAM */
|
||
u16 bam = rad_r16_to_bam(r);
|
||
|
||
/* Decompose BAM into quadrant + in-quadrant */
|
||
u32 q = ((u32)bam >> 14) & 0x3;
|
||
u32 inq = (u32)bam & 0x3FFFu;
|
||
s32 tsign = 1; /* tan sign from quadrant */
|
||
|
||
/* Exact zeros: bam lands on 0° or 180° */
|
||
if (inq == 0 && (q == 0 || q == 2))
|
||
return 0;
|
||
|
||
/* Q1/Q3: reflect and negate */
|
||
if (q == 1 || q == 3) {
|
||
inq = 0x4000u - inq;
|
||
tsign = -1;
|
||
}
|
||
|
||
/* Octant table lookup + interpolation (same logic as fr_tan_bam) */
|
||
u32 idx, frac;
|
||
s32 raw;
|
||
|
||
if (inq < FR_TAN_OCTANT) {
|
||
/* First octant [0°, 45°): direct lookup */
|
||
idx = inq >> FR_TAN_FRAC_BITS;
|
||
frac = inq & FR_TAN_FRAC_MASK;
|
||
s32 lo = (s32)gFR_TAN_TAB_O[idx];
|
||
s32 hi = (s32)gFR_TAN_TAB_O[idx + 1];
|
||
raw = lo + (((hi - lo) * (s32)frac + FR_TAN_FRAC_HALF) >> FR_TAN_FRAC_BITS);
|
||
|
||
if (raw < 0x40) {
|
||
s32 lo4 = lo << 4;
|
||
s32 hi4 = hi << 4;
|
||
raw = lo4 + (((hi4 - lo4) * (s32)frac + FR_TAN_FRAC_HALF) >> FR_TAN_FRAC_BITS);
|
||
raw = (raw + 4) >> 3;
|
||
} else {
|
||
raw <<= 1;
|
||
}
|
||
} else {
|
||
/* Second octant [45°, 90°): reciprocal identity */
|
||
u32 comp = 0x4000u - inq;
|
||
idx = comp >> FR_TAN_FRAC_BITS;
|
||
frac = comp & FR_TAN_FRAC_MASK;
|
||
s32 lo = (s32)gFR_TAN_TAB_O[idx];
|
||
s32 hi = (s32)gFR_TAN_TAB_O[idx + 1];
|
||
raw = lo + (((hi - lo) * (s32)frac + FR_TAN_FRAC_HALF) >> FR_TAN_FRAC_BITS);
|
||
|
||
if (raw < 0x40) {
|
||
s32 lo4 = lo << 4;
|
||
s32 hi4 = hi << 4;
|
||
s32 raw_hp = lo4 + (((hi4 - lo4) * (s32)frac + FR_TAN_FRAC_HALF) >> FR_TAN_FRAC_BITS);
|
||
if (raw_hp < 32)
|
||
raw = FR_TRIG_MAXVAL;
|
||
else
|
||
raw = (s32)((0x80000000u / (u32)raw_hp) << 4);
|
||
} else {
|
||
raw = (s32)(0x80000000u / (u32)raw);
|
||
}
|
||
}
|
||
|
||
/* Combine quadrant sign and input sign */
|
||
s32 v = (tsign < 0) ? -raw : raw;
|
||
return (sign < 0) ? -v : v;
|
||
}
|
||
|
||
#ifndef FR_LEAN
|
||
/*=======================================================
|
||
* Degree-input trig: convert to u16 BAM via fr_deg_to_bam, then
|
||
* call the BAM-native functions. Cardinal angles are exact.
|
||
*/
|
||
|
||
s32 fr_cos_deg(s32 deg, u16 radix)
|
||
{
|
||
if (radix == 0) return fr_cos_bam(FR_DEG2BAM_I(deg));
|
||
if (deg < 0) deg = -deg;
|
||
/* Exact cardinal angles */
|
||
s32 frac_mask = (1 << radix) - 1;
|
||
if ((deg & frac_mask) == 0) {
|
||
s32 rem = (deg >> radix) % 360;
|
||
if (rem == 0) return FR_TRIG_ONE;
|
||
if (rem == 90) return 0;
|
||
if (rem == 180) return -FR_TRIG_ONE;
|
||
if (rem == 270) return 0;
|
||
}
|
||
/* Near 90° or 270° (cos=0 crossings): cos(90+δ) = -sin(δ) ≈ -δ·π/180,
|
||
* cos(270+δ) = sin(δ) ≈ δ·π/180. Avoids BAM rounding error at zero. */
|
||
s32 d = normalize_to_r16(deg, radix);
|
||
if (d >= FR_D360_R16) { s32 n = d / FR_D360_R16; d -= n * FR_D360_R16; }
|
||
{
|
||
const s32 DEG_THRESH = 14000; /* ~0.21° at r16 */
|
||
s32 delta = d - FR_D90_R16;
|
||
if (delta >= -DEG_THRESH && delta <= DEG_THRESH) {
|
||
s32 dr = (s32)(((s64)delta * FR_kDEG2RAD + (1 << 15)) >> 16);
|
||
return -dr;
|
||
}
|
||
delta = d - (FR_D90_R16 + FR_D180_R16);
|
||
if (delta >= -DEG_THRESH && delta <= DEG_THRESH) {
|
||
s32 dr = (s32)(((s64)delta * FR_kDEG2RAD + (1 << 15)) >> 16);
|
||
return dr;
|
||
}
|
||
}
|
||
return fr_cos_bam(fr_deg_to_bam(deg, radix));
|
||
}
|
||
|
||
s32 fr_sin_deg(s32 deg, u16 radix)
|
||
{
|
||
if (radix == 0) return fr_sin_bam(FR_DEG2BAM_I(deg));
|
||
s32 sign = 1;
|
||
if (deg < 0) { deg = -deg; sign = -1; }
|
||
/* Exact cardinal angles */
|
||
s32 frac_mask = (1 << radix) - 1;
|
||
if ((deg & frac_mask) == 0) {
|
||
s32 rem = (deg >> radix) % 360;
|
||
if (rem == 0) return 0;
|
||
if (rem == 90) return (sign < 0) ? -FR_TRIG_ONE : FR_TRIG_ONE;
|
||
if (rem == 180) return 0;
|
||
if (rem == 270) return (sign < 0) ? FR_TRIG_ONE : -FR_TRIG_ONE;
|
||
}
|
||
s32 v = fr_sin_bam(fr_deg_to_bam(deg, radix));
|
||
return (sign < 0) ? -v : v;
|
||
}
|
||
|
||
s32 FR_TanI(s32 deg)
|
||
{
|
||
/* Exact pole: deg mod 180 == ±90. Sign matches input sign. */
|
||
s32 rem = deg % 180;
|
||
if (rem == 90 || rem == -90)
|
||
return (deg > 0) ? FR_TRIG_MAXVAL : -FR_TRIG_MAXVAL;
|
||
return fr_tan_bam(FR_DEG2BAM_I(deg));
|
||
}
|
||
|
||
s32 fr_tan_deg(s32 deg, u16 radix)
|
||
{
|
||
if (radix == 0) return FR_TanI(deg);
|
||
s32 deg_orig = deg;
|
||
/* Normalize to [0, 360°) at caller radix */
|
||
s32 d360 = 360 << radix;
|
||
if (deg < 0) {
|
||
deg += ((-deg) / d360) * d360;
|
||
if (deg < 0) deg += d360;
|
||
}
|
||
if (deg >= d360) {
|
||
deg -= (deg / d360) * d360;
|
||
}
|
||
/* Exact cardinal angles */
|
||
s32 frac_mask = (1 << radix) - 1;
|
||
if ((deg & frac_mask) == 0) {
|
||
s32 ideg = deg >> radix;
|
||
if (ideg == 0 || ideg == 180) return 0;
|
||
if (ideg == 90 || ideg == 270)
|
||
return (deg_orig >= 0) ? FR_TRIG_MAXVAL : -FR_TRIG_MAXVAL;
|
||
}
|
||
/* Near 0° or 180° (tan=0 crossings): tan(δ) ≈ δ in radians */
|
||
s32 d = normalize_to_r16(deg, radix);
|
||
{
|
||
const s32 DEG_THRESH = 14000; /* ~0.21° at r16 */
|
||
s32 delta;
|
||
/* Near 0° */
|
||
if (d < DEG_THRESH) {
|
||
s32 up = d << 8;
|
||
return (FR_DEG2RAD(up) + (1 << 7)) >> 8;
|
||
}
|
||
/* Near 180° */
|
||
delta = d - FR_D180_R16;
|
||
if (delta >= -DEG_THRESH && delta <= DEG_THRESH) {
|
||
s32 up = delta << 8;
|
||
return (FR_DEG2RAD(up) + (1 << 7)) >> 8;
|
||
}
|
||
/* Near 360° */
|
||
delta = FR_D360_R16 - d;
|
||
if (delta >= 0 && delta < DEG_THRESH) {
|
||
s32 up = delta << 8;
|
||
return -((FR_DEG2RAD(up) + (1 << 7)) >> 8);
|
||
}
|
||
}
|
||
/* Main path: convert to u16 BAM, table lookup */
|
||
u16 bam = fr_deg_to_bam(deg, radix);
|
||
s32 v = fr_tan_bam(bam);
|
||
/* Near-pole BAM alias: determine sign from normalized angle position */
|
||
if (bam == 0x4000u || bam == 0xC000u) {
|
||
s32 pole_d = (bam == 0x4000u) ? FR_D90_R16 : (FR_D90_R16 + FR_D180_R16);
|
||
v = (d < pole_d) ? FR_TRIG_MAXVAL : -FR_TRIG_MAXVAL;
|
||
}
|
||
return v;
|
||
}
|
||
#endif /* FR_LEAN */
|
||
|
||
/*=======================================================
|
||
* FR_FixMuls (x*y signed, NOT saturated, round-to-nearest)
|
||
*
|
||
* Treats x and y as fixed-point values at the same radix r and returns
|
||
* (x*y) >> r at radix r. The user is responsible for tracking the radix
|
||
* point and for guaranteeing the product fits in 32 bits.
|
||
*
|
||
* Adds 0.5 LSB (0x8000) before the shift so the result rounds to
|
||
* nearest instead of truncating toward zero.
|
||
*/
|
||
s32 FR_FixMuls(s32 x, s32 y)
|
||
{
|
||
int64_t v = (int64_t)x * (int64_t)y;
|
||
return (s32)((v + 0x8000) >> 16);
|
||
}
|
||
|
||
/*=======================================================
|
||
* FR_FixMulSat (x*y signed, SATURATED, round-to-nearest)
|
||
*
|
||
* Same semantics as FR_FixMuls but clamps to [INT32_MIN, INT32_MAX] on
|
||
* overflow instead of wrapping. The fixed-point radix is fixed at 16 bits
|
||
* (sM.16 inputs and output). Rounds to nearest (adds 0.5 LSB before shift).
|
||
*/
|
||
s32 FR_FixMulSat(s32 x, s32 y)
|
||
{
|
||
int64_t v = ((int64_t)x * (int64_t)y + 0x8000) >> 16;
|
||
if (v > (int64_t)0x7fffffff) return FR_OVERFLOW_POS;
|
||
if (v < -(int64_t)0x80000000) return FR_OVERFLOW_NEG;
|
||
return (s32)v;
|
||
}
|
||
|
||
/*=======================================================
|
||
FR_FixAddSat (x+y saturated add)
|
||
programmer must align radix points before using this function
|
||
*/
|
||
s32 FR_FixAddSat(s32 x, s32 y)
|
||
{
|
||
s32 sum = x + y;
|
||
if (x < 0)
|
||
{
|
||
if (y < 0)
|
||
return (sum >= 0) ? FR_OVERFLOW_NEG : sum;
|
||
}
|
||
else
|
||
{
|
||
if (y >= 0)
|
||
return (sum <= 0) ? FR_OVERFLOW_POS : sum;
|
||
}
|
||
return sum;
|
||
}
|
||
|
||
/* Inverse Trig
|
||
* acos with binary search of the BAM-native quadrant table.
|
||
*
|
||
* Algorithm: bring `input` into s0.15, then binary-search the first-quadrant
|
||
* cos table for the table entry closest to |input|. Apply quadrant mirror
|
||
* if input was negative.
|
||
*/
|
||
/* FR_acos — returns radians at out_radix.
|
||
* Range: [0, pi]. Input is a cosine value at the given radix.
|
||
*
|
||
* Uses the 129-entry sine table in reverse: binary-search the ascending
|
||
* table to find asin(|input|), then acos = pi/2 - asin (with sign handling
|
||
* for the second quadrant).
|
||
*/
|
||
s32 FR_acos(s32 input, u16 radix, u16 out_radix)
|
||
{
|
||
s32 v;
|
||
s16 sign;
|
||
s32 lo, hi, mid;
|
||
s32 idx, d, num, frac;
|
||
s32 input_abs;
|
||
|
||
/* Work with absolute value at the caller's radix */
|
||
sign = (s16)((input < 0) ? 1 : 0);
|
||
input_abs = sign ? -input : input;
|
||
|
||
/* Clamp at the caller's radix */
|
||
{
|
||
s32 one = (s32)1 << radix;
|
||
if (input_abs >= one)
|
||
return sign ? FR_CHRDX(FR_kPI, FR_kPREC, out_radix) : 0;
|
||
}
|
||
|
||
v = FR_CHRDX(input_abs, radix, FR_TRIG_PREC); /* |input| at s0.15 */
|
||
|
||
/* Small-angle fast path: when cos(θ) is close to 1.0, the sine table
|
||
* has poor resolution near the top (entries close together).
|
||
* Use acos(x) ≈ sqrt(2*(1-x)) instead. Threshold: v > sin_tab[121]
|
||
* means the input is > cos(7*π/256) ≈ 0.9975. */
|
||
if (v > gFR_SIN_TAB_Q[FR_TRIG_TABLE_SIZE - 8])
|
||
{
|
||
s32 one = (s32)1 << radix;
|
||
s32 one_minus_x = one - input_abs; /* 1-|x| at caller radix */
|
||
s32 two_omx = one_minus_x << 1; /* 2(1-|x|) at caller radix */
|
||
s32 rad_native = FR_sqrt(two_omx, radix); /* radians at caller radix */
|
||
s32 rad_out = FR_CHRDX(rad_native, radix, out_radix);
|
||
if (sign)
|
||
rad_out = FR_CHRDX(FR_kPI, FR_kPREC, out_radix) - rad_out;
|
||
return rad_out;
|
||
}
|
||
|
||
/* Binary search on the ascending sine table.
|
||
* gFR_SIN_TAB_Q[0] = 0 (sin 0°), gFR_SIN_TAB_Q[128] = 32768 (sin 90°).
|
||
*
|
||
* Find the first index where table[idx] >= v. */
|
||
lo = 0;
|
||
hi = FR_TRIG_TABLE_SIZE;
|
||
while (lo < hi)
|
||
{
|
||
mid = (lo + hi) >> 1;
|
||
if ((s32)gFR_SIN_TAB_Q[mid] < v)
|
||
lo = mid + 1;
|
||
else
|
||
hi = mid;
|
||
}
|
||
|
||
/* lo is now the first index where table[lo] >= v.
|
||
* The bracketing interval is [lo-1, lo] with table[lo-1] < v <= table[lo].
|
||
* This gives us the asin angle; acos = pi/2 - asin. */
|
||
idx = lo;
|
||
if (idx <= 0)
|
||
{
|
||
idx = 0;
|
||
frac = 0;
|
||
}
|
||
else if (idx >= FR_TRIG_TABLE_SIZE)
|
||
{
|
||
idx = FR_TRIG_TABLE_SIZE - 1;
|
||
frac = 0;
|
||
}
|
||
else
|
||
{
|
||
/* Interpolate between table[idx-1] and table[idx].
|
||
* d = table[idx] - table[idx-1] (>= 0, sin increasing)
|
||
* num = v - table[idx-1] (how far past table[idx-1])
|
||
*/
|
||
d = (s32)gFR_SIN_TAB_Q[idx] - (s32)gFR_SIN_TAB_Q[idx - 1];
|
||
num = v - (s32)gFR_SIN_TAB_Q[idx - 1];
|
||
if (d > 0)
|
||
frac = ((num << FR_TRIG_FRAC_BITS) + (d >> 1)) / d;
|
||
else
|
||
frac = 0;
|
||
idx = idx - 1;
|
||
}
|
||
|
||
{
|
||
/* asin_bam is the angle in first-quadrant BAM whose sin = v */
|
||
u16 asin_bam = (u16)(((u32)idx << FR_TRIG_FRAC_BITS) + (u32)frac);
|
||
/* acos = pi/2 - asin (in BAM: quadrant - asin_bam) */
|
||
u16 bam = (u16)(FR_TRIG_QUADRANT - asin_bam);
|
||
if (sign)
|
||
bam = (u16)(FR_BAM_HALF - bam); /* mirror: pi - angle */
|
||
return FR_CHRDX(FR_Q2RAD(bam), 14, out_radix);
|
||
}
|
||
}
|
||
|
||
/* FR_asin — returns radians at out_radix. Range: [-pi/2, pi/2]. */
|
||
s32 FR_asin(s32 input, u16 radix, u16 out_radix)
|
||
{
|
||
/* asin(x) = pi/2 - acos(x) */
|
||
s32 half_pi = FR_CHRDX(FR_kQ2RAD, FR_kPREC, out_radix);
|
||
return half_pi - FR_acos(input, radix, out_radix);
|
||
}
|
||
|
||
/* FR_atan2(y, x, out_radix) — full-circle arctangent, returns radians
|
||
* at the specified output radix (s32).
|
||
*
|
||
* Range: [-pi, pi]. Returns 0 for atan2(0,0).
|
||
*
|
||
* Implementation: normalise (x,y) via FR_hypot_fast8, then recover the
|
||
* angle with FR_asin or FR_acos (both use the 129-entry cosine table).
|
||
* To stay in the well-conditioned region of each inverse function we
|
||
* switch at 45°:
|
||
* |y| <= |x| → use asin(y/h) — asin stable near 0
|
||
* |y| > |x| → use acos(x/h) — acos stable near pi/2
|
||
* This keeps the derivative amplification factor below 1.414x everywhere.
|
||
*/
|
||
s32 FR_atan2(s32 y, s32 x, u16 out_radix)
|
||
{
|
||
s32 ax, ay, h, q1_angle;
|
||
|
||
/* Axis cases — exact angles, no divide. */
|
||
if (x == 0)
|
||
{
|
||
if (y > 0) return FR_CHRDX(FR_kQ2RAD, FR_kPREC, out_radix); /* pi/2 */
|
||
if (y < 0) return -FR_CHRDX(FR_kQ2RAD, FR_kPREC, out_radix); /* -pi/2 */
|
||
return 0;
|
||
}
|
||
if (y == 0)
|
||
return (x > 0) ? 0 : FR_CHRDX(FR_kPI, FR_kPREC, out_radix); /* 0 or pi */
|
||
|
||
ax = (x < 0) ? -x : x;
|
||
ay = (y < 0) ? -y : y;
|
||
|
||
/* Normalise so max(ax,ay) sits in [2^14, 2^15). This gives
|
||
* FR_hypot_fast8 enough integer bits for the shift-only segments
|
||
* to produce an accurate ratio — critical when the raw inputs are
|
||
* small (e.g. atan2(1,1) at radix 0). Scaling both by the same
|
||
* power of two doesn't change the angle. */
|
||
{
|
||
s32 mx = (ax > ay) ? ax : ay;
|
||
while (mx < (1L << 14)) { ax <<= 1; ay <<= 1; mx <<= 1; }
|
||
while (mx >= (1L << 16)) { ax >>= 1; ay >>= 1; mx >>= 1; }
|
||
}
|
||
|
||
h = FR_hypot_fast8((s32)ax, (s32)ay);
|
||
if (h == 0) return 0; /* degenerate */
|
||
|
||
/* Compute the first-quadrant angle (positive, [0..pi/2]).
|
||
* Divide produces a value in [0,1] at radix FR_TRIG_PREC (s0.15).
|
||
*
|
||
* Small-angle fast path: when the minor-axis ratio is small,
|
||
* asin(x) ≈ x (error < x³/6). Below ~5° the cubic term is
|
||
* smaller than the table-lookup error, so the direct identity
|
||
* is both faster and more accurate. Threshold 2753 at r15
|
||
* corresponds to sin(~4.8°) = 0.084. */
|
||
#define FR_ATAN2_SMALL 2753
|
||
if (ay <= ax)
|
||
{
|
||
/* angle in [0°..45°]: use asin(ay/h) — well-conditioned near 0 */
|
||
s32 sin_val = (s32)(((int64_t)ay << FR_TRIG_PREC) / h);
|
||
if (sin_val < FR_ATAN2_SMALL)
|
||
q1_angle = FR_CHRDX(sin_val, FR_TRIG_PREC, out_radix);
|
||
else
|
||
q1_angle = FR_asin(sin_val, FR_TRIG_PREC, out_radix);
|
||
}
|
||
else
|
||
{
|
||
/* angle in [45°..90°]: use acos(ax/h) — well-conditioned near pi/2 */
|
||
s32 cos_val = (s32)(((int64_t)ax << FR_TRIG_PREC) / h);
|
||
if (cos_val < FR_ATAN2_SMALL)
|
||
{
|
||
/* angle ≈ pi/2 - cos_val (symmetric small-angle identity) */
|
||
s32 half_pi = FR_CHRDX(FR_kQ2RAD, FR_kPREC, out_radix);
|
||
q1_angle = half_pi - FR_CHRDX(cos_val, FR_TRIG_PREC, out_radix);
|
||
}
|
||
else
|
||
q1_angle = FR_acos(cos_val, FR_TRIG_PREC, out_radix);
|
||
}
|
||
|
||
/* Apply quadrant from signs of x and y.
|
||
* q1_angle is always positive [0..pi/2]. */
|
||
{
|
||
s32 pi = FR_CHRDX(FR_kPI, FR_kPREC, out_radix);
|
||
if (x > 0)
|
||
return (y > 0) ? q1_angle : -q1_angle;
|
||
/* x < 0: mirror across y-axis */
|
||
return (y > 0) ? (pi - q1_angle) : (q1_angle - pi);
|
||
}
|
||
}
|
||
|
||
/* FR_atan(input, radix, out_radix) — arctangent of a single argument.
|
||
* Returns radians at out_radix, range [-pi/2, pi/2].
|
||
*/
|
||
s32 FR_atan(s32 input, u16 radix, u16 out_radix)
|
||
{
|
||
s32 one = (s32)1 << radix;
|
||
return FR_atan2(input, one, out_radix);
|
||
}
|
||
|
||
/* 2^f table for f in [0, 1] in 65 entries (64 segments), output in s.16
|
||
* fixed point. Entry i = round(2^(i/64) * 65536). Size: 260 bytes.
|
||
* Used by FR_pow2 to look up the fractional power of 2 with linear
|
||
* interpolation.
|
||
*/
|
||
static const u32 gFR_POW2_FRAC_TAB[65] = {
|
||
65536, 66250, 66971, 67700, 68438, 69183, 69936, 70698,
|
||
71468, 72246, 73032, 73828, 74632, 75444, 76266, 77096,
|
||
77936, 78785, 79642, 80510, 81386, 82273, 83169, 84074,
|
||
84990, 85915, 86851, 87796, 88752, 89719, 90696, 91684,
|
||
92682, 93691, 94711, 95743, 96785, 97839, 98905, 99982,
|
||
101070, 102171, 103283, 104408, 105545, 106694, 107856, 109031,
|
||
110218, 111418, 112631, 113858, 115098, 116351, 117618, 118899,
|
||
120194, 121502, 122825, 124163, 125515, 126882, 128263, 129660,
|
||
131072
|
||
};
|
||
|
||
/* FR_pow2(input, radix) — computes 2^(input/2^radix), result at same radix.
|
||
*
|
||
* Algorithm: split input into floor(integer) and fractional part. The
|
||
* fractional part is in [0, 1) by construction (Euclidean / mathematical
|
||
* floor — the fractional part of -2.3 is +0.7, not -0.3). Then
|
||
* 2^(int + frac) = 2^int * 2^frac
|
||
* where 2^frac is looked up from a 65-entry table at radix 16, and 2^int
|
||
* is a shift.
|
||
*
|
||
* Worst-case absolute error: ~1e-5 over [-8, 8] (65-entry table).
|
||
* Linear interpolation leaves a small concavity error in each interval.
|
||
*/
|
||
s32 FR_pow2(s32 input, u16 radix)
|
||
{
|
||
s32 flr, frac_full, idx, frac_lo, lo, hi, mant, result;
|
||
u32 mask = (radix > 0) ? (((u32)1 << radix) - 1) : 0;
|
||
|
||
/* Mathematical floor: for positive input it's input>>radix; for
|
||
* negative input we need to round toward -infinity, not toward zero.
|
||
*/
|
||
if (input >= 0)
|
||
{
|
||
flr = (s32)((u32)input >> radix);
|
||
frac_full = (s32)((u32)input & mask);
|
||
}
|
||
else
|
||
{
|
||
s32 neg = -input;
|
||
s32 nflr = (s32)((u32)neg >> radix);
|
||
s32 nfrc = (s32)((u32)neg & mask);
|
||
if (nfrc == 0)
|
||
{
|
||
flr = -nflr;
|
||
frac_full = 0;
|
||
}
|
||
else
|
||
{
|
||
flr = -nflr - 1; /* floor toward -inf */
|
||
frac_full = (s32)((1L << radix) - nfrc);
|
||
}
|
||
}
|
||
|
||
/* frac_full is in [0, 2^radix). Re-radix it to s.16 for table lookup. */
|
||
if (radix > 16)
|
||
frac_full >>= (radix - 16);
|
||
else if (radix < 16)
|
||
frac_full <<= (16 - radix);
|
||
/* now frac_full is in [0, 65536) representing fractional in s.16. */
|
||
|
||
/* Top 6 bits index the table; bottom 10 are the interpolation fraction. */
|
||
idx = frac_full >> 10;
|
||
frac_lo = frac_full & ((1L << 10) - 1);
|
||
lo = (s32)gFR_POW2_FRAC_TAB[idx];
|
||
hi = (s32)gFR_POW2_FRAC_TAB[idx + 1];
|
||
mant = lo + (((hi - lo) * frac_lo) >> 10); /* mant in s.16, in [1.0, 2.0) */
|
||
|
||
/* Apply integer shift. mant is at radix 16. We want output at `radix`.
|
||
* If radix == 16: just shift mant.
|
||
* Otherwise re-radix mant first.
|
||
*/
|
||
if (flr >= 0)
|
||
{
|
||
/* result = mant << flr, then re-radix to caller's radix. */
|
||
if (flr >= 30)
|
||
return FR_OVERFLOW_POS;
|
||
result = mant << flr;
|
||
return FR_CHRDX(result, 16, radix);
|
||
}
|
||
else
|
||
{
|
||
/* mant >> -flr at radix 16, then re-radix. */
|
||
s32 sh = -flr;
|
||
if (sh >= 30)
|
||
return 0; /* underflow */
|
||
result = mant >> sh;
|
||
return FR_CHRDX(result, 16, radix);
|
||
}
|
||
}
|
||
|
||
/* log2 mantissa table for m in [1, 2), m = 1 + i/64, returning log2(m)
|
||
* in s.16 fixed point. 65 entries (last is log2(2) = 1.0 = 65536) so the
|
||
* interpolation between idx and idx+1 never reads out of bounds.
|
||
* Size: 260 bytes. Entry i = round(log2(1 + i/64) * 65536).
|
||
*/
|
||
static const u32 gFR_LOG2_MANT_TAB[65] = {
|
||
0, 1466, 2909, 4331, 5732, 7112, 8473, 9814,
|
||
11136, 12440, 13727, 14996, 16248, 17484, 18704, 19909,
|
||
21098, 22272, 23433, 24579, 25711, 26830, 27936, 29029,
|
||
30109, 31178, 32234, 33279, 34312, 35334, 36346, 37346,
|
||
38336, 39316, 40286, 41246, 42196, 43137, 44068, 44990,
|
||
45904, 46809, 47705, 48593, 49472, 50344, 51207, 52063,
|
||
52911, 53751, 54584, 55410, 56229, 57040, 57845, 58643,
|
||
59434, 60219, 60997, 61769, 62534, 63294, 64047, 64794,
|
||
65536
|
||
};
|
||
|
||
/* FR_log2(input, radix, output_radix) — log base 2 of a fixed-point number.
|
||
*
|
||
* input : value to take log2 of, treated as a positive sM.radix value.
|
||
* radix : number of fractional bits in `input`.
|
||
* output_radix : number of fractional bits in the result.
|
||
*
|
||
* Returns FR_LOG2MIN for input <= 0 (log of zero/negative is undefined; we
|
||
* return a large negative sentinel rather than crash).
|
||
*
|
||
* Algorithm:
|
||
* 1. Find p, the position of the leading 1 bit of `input`.
|
||
* log2(input) = p + log2(input / 2^p), where the second term is in
|
||
* [0, 1) because (input / 2^p) is in [1, 2).
|
||
* 2. Normalize the mantissa to s1.31 by shifting `input` so its top bit
|
||
* sits at bit 31 (so bits 30..25 are the upper 6 bits of m-1).
|
||
* 3. Look up log2(m) in the 65-entry table with linear interpolation
|
||
* across the next 24 bits. Result is in s.16.
|
||
* 4. integer_part = (p - radix), then result = (integer_part << 16) +
|
||
* mantissa_log2.
|
||
* 5. Re-radix to the requested output_radix via FR_CHRDX.
|
||
*
|
||
* Worst-case absolute error: ~6e-5 in log2 units (65-entry table).
|
||
*/
|
||
s32 FR_log2(s32 input, u16 radix, u16 output_radix)
|
||
{
|
||
s32 p, integer_part, idx, frac, lo, hi, mant_log2, result;
|
||
u32 m, u;
|
||
|
||
if (input <= 0)
|
||
return FR_LOG2MIN;
|
||
|
||
/* Step 1: find the position of the leading 1 bit. */
|
||
u = (u32)input;
|
||
p = 0;
|
||
while (u > 1)
|
||
{
|
||
u >>= 1;
|
||
p++;
|
||
}
|
||
|
||
/* Step 2: shift input so the leading 1 bit is at bit 30 (s1.30 mantissa).
|
||
* Equivalently: m = input << (30 - p), where m is in [2^30, 2^31).
|
||
* The fractional part of m / 2^30 is in [0, 1), and that's what we look
|
||
* up in the table.
|
||
*/
|
||
if (p >= 30)
|
||
m = (u32)input >> (p - 30);
|
||
else
|
||
m = (u32)input << (30 - p);
|
||
|
||
/* m is now in [2^30, 2^31). Subtract 2^30 to get the fractional part
|
||
* (m_frac in [0, 2^30)). Index into the 64-entry table is the top 6
|
||
* bits of m_frac; the lower 24 bits are the interpolation fraction.
|
||
*/
|
||
m -= (1u << 30);
|
||
idx = (s32)(m >> 24); /* 6 bits */
|
||
frac = (s32)(m & ((1u << 24) - 1)); /* 24 bits */
|
||
lo = (s32)gFR_LOG2_MANT_TAB[idx];
|
||
hi = (s32)gFR_LOG2_MANT_TAB[idx + 1];
|
||
mant_log2 = lo + (s32)(((int64_t)(hi - lo) * frac) >> 24);
|
||
|
||
/* Step 3: assemble. integer_part = p - radix. */
|
||
integer_part = p - (s32)radix;
|
||
result = (integer_part << 16) + mant_log2;
|
||
|
||
/* Step 4: re-radix to output_radix. */
|
||
return FR_CHRDX(result, 16, output_radix);
|
||
}
|
||
|
||
s32 FR_ln(s32 input, u16 radix, u16 output_radix)
|
||
{
|
||
s32 r = FR_log2(input, radix, output_radix);
|
||
return FR_MULK28(r, FR_krLOG2E_28);
|
||
}
|
||
|
||
#ifndef FR_LEAN
|
||
s32 FR_log10(s32 input, u16 radix, u16 output_radix)
|
||
{
|
||
s32 r = FR_log2(input, radix, output_radix);
|
||
return FR_MULK28(r, FR_krLOG2_10_28);
|
||
}
|
||
#endif
|
||
|
||
#ifndef FR_NO_PRINT
|
||
/***************************************
|
||
* FR_printNumD - write a decimal integer with space padding.
|
||
*
|
||
* Equivalent to "%*d" in printf, modulo the return convention.
|
||
*
|
||
* f : per-character output function (e.g. putchar). Must not be NULL.
|
||
* n : signed integer to print.
|
||
* pad : minimum field width; spaces are prepended to reach this width.
|
||
*
|
||
* Returns the number of characters written on success, or -1 if `f` is NULL.
|
||
*/
|
||
int FR_printNumD(int (*f)(char), int n, int pad)
|
||
{
|
||
unsigned int mag;
|
||
int written = 0, neg = 0;
|
||
int digits = 1;
|
||
unsigned int t;
|
||
|
||
if (!f)
|
||
return -1;
|
||
|
||
if (n < 0)
|
||
{
|
||
neg = 1;
|
||
mag = (unsigned int)(-(long)n); /* safe for INT_MIN */
|
||
}
|
||
else
|
||
{
|
||
mag = (unsigned int)n;
|
||
}
|
||
|
||
/* Count decimal digits in mag (always at least 1 for n=0). */
|
||
t = mag;
|
||
while (t >= 10)
|
||
{
|
||
t /= 10;
|
||
digits++;
|
||
}
|
||
|
||
/* Pad with spaces. The total width includes the sign. */
|
||
{
|
||
int total = digits + (neg ? 1 : 0);
|
||
while (pad-- > total)
|
||
{
|
||
f(' ');
|
||
written++;
|
||
}
|
||
}
|
||
|
||
if (neg)
|
||
{
|
||
f('-');
|
||
written++;
|
||
}
|
||
|
||
/* Print digits MSB first by computing the largest power of 10 <= mag. */
|
||
{
|
||
unsigned int p = 1;
|
||
int i;
|
||
for (i = 1; i < digits; i++)
|
||
p *= 10;
|
||
while (p > 0)
|
||
{
|
||
f((char)('0' + (mag / p) % 10));
|
||
written++;
|
||
if (p == 1)
|
||
break;
|
||
p /= 10;
|
||
}
|
||
}
|
||
|
||
return written;
|
||
}
|
||
|
||
/***************************************
|
||
* FR_printNumF - write a fixed-point number as a decimal floating-point string.
|
||
*
|
||
* f : per-character output function. Must not be NULL.
|
||
* n : signed fixed-point value at the given radix.
|
||
* radix : number of fractional bits in `n`.
|
||
* pad : minimum field width (including sign and decimal point).
|
||
* prec : number of fractional digits to print.
|
||
*
|
||
* Returns the number of characters written on success, -1 if `f` is NULL.
|
||
*
|
||
* Rounding policy: truncates fractional digits beyond `prec` (no rounding).
|
||
*/
|
||
int FR_printNumF(int (*f)(char), s32 n, int radix, int pad, int prec)
|
||
{
|
||
unsigned int mag_int;
|
||
u32 mag_frac;
|
||
u32 frac_mask;
|
||
int written = 0, neg = 0;
|
||
int int_digits = 1;
|
||
int total;
|
||
unsigned int t;
|
||
|
||
if (!f)
|
||
return -1;
|
||
|
||
frac_mask = (radix > 0) ? (((u32)1 << radix) - 1) : 0;
|
||
|
||
if (n < 0)
|
||
{
|
||
neg = 1;
|
||
/* Negate as unsigned to avoid INT_MIN overflow. */
|
||
u32 un = (u32)(-(int64_t)n);
|
||
mag_int = (unsigned int)(un >> radix);
|
||
mag_frac = un & frac_mask;
|
||
}
|
||
else
|
||
{
|
||
mag_int = (unsigned int)((u32)n >> radix);
|
||
mag_frac = (u32)n & frac_mask;
|
||
}
|
||
|
||
/* Count integer digits. */
|
||
t = mag_int;
|
||
while (t >= 10)
|
||
{
|
||
t /= 10;
|
||
int_digits++;
|
||
}
|
||
|
||
/* Total visible width = sign + int + (dot + prec digits if prec>0). */
|
||
total = int_digits + (neg ? 1 : 0) + ((prec > 0) ? (1 + prec) : 0);
|
||
while (pad-- > total)
|
||
{
|
||
f(' ');
|
||
written++;
|
||
}
|
||
|
||
if (neg)
|
||
{
|
||
f('-');
|
||
written++;
|
||
}
|
||
|
||
/* Print integer part. */
|
||
{
|
||
unsigned int p = 1;
|
||
int i;
|
||
for (i = 1; i < int_digits; i++)
|
||
p *= 10;
|
||
while (p > 0)
|
||
{
|
||
f((char)('0' + (mag_int / p) % 10));
|
||
written++;
|
||
if (p == 1)
|
||
break;
|
||
p /= 10;
|
||
}
|
||
}
|
||
|
||
/* Print fractional part. Extract one decimal digit at a time:
|
||
* frac' = frac * 10
|
||
* digit = frac' >> radix
|
||
* frac = frac' & frac_mask
|
||
*/
|
||
if (prec > 0)
|
||
{
|
||
f('.');
|
||
written++;
|
||
while (prec-- > 0)
|
||
{
|
||
u32 scaled;
|
||
int digit;
|
||
scaled = (u32)(((uint64_t)mag_frac * 10));
|
||
digit = (int)(scaled >> radix);
|
||
mag_frac = scaled & frac_mask;
|
||
f((char)('0' + (digit % 10)));
|
||
written++;
|
||
}
|
||
}
|
||
|
||
return written;
|
||
}
|
||
|
||
/***************************************
|
||
* FR_printNumH - write an integer as hexadecimal.
|
||
*
|
||
* f : per-character output function. Must not be NULL.
|
||
* n : integer to print (interpreted as unsigned for the digits).
|
||
* showPrefix : if non-zero, prepend "0x".
|
||
*
|
||
* Returns the number of characters written on success, -1 if f is NULL.
|
||
*/
|
||
int FR_printNumH(int (*f)(char), int n, int showPrefix)
|
||
{
|
||
unsigned int u = (unsigned int)n;
|
||
int written = 0;
|
||
int x = (int)((sizeof(int) << 1) - 1);
|
||
int d;
|
||
|
||
if (!f)
|
||
return -1;
|
||
|
||
if (showPrefix)
|
||
{
|
||
f('0');
|
||
f('x');
|
||
written += 2;
|
||
}
|
||
|
||
do
|
||
{
|
||
d = (int)((u >> (x << 2)) & 0xf);
|
||
d = (d > 9) ? (d - 0xa + 'a') : (d + '0');
|
||
f((char)d);
|
||
written++;
|
||
} while (x--);
|
||
|
||
return written;
|
||
}
|
||
|
||
/*=======================================================
|
||
* FR_numstr — parse a decimal string into a fixed-point value.
|
||
*
|
||
* This is the runtime inverse of FR_printNumF: given a string like
|
||
* "12.34" or "-0.05" and a radix (number of fractional bits), it
|
||
* returns the s32 fixed-point representation.
|
||
*
|
||
* Features:
|
||
* - Leading whitespace is skipped.
|
||
* - Optional sign ('+' or '-').
|
||
* - Up to 9 fractional digits are used (s32 range).
|
||
* - No malloc, no strtod, no libm.
|
||
*
|
||
* Returns 0 for NULL or empty input.
|
||
*/
|
||
s32 FR_numstr(const char *s, u16 radix)
|
||
{
|
||
static const s32 pow10[10] = {
|
||
1L, 10L, 100L, 1000L, 10000L,
|
||
100000L, 1000000L, 10000000L, 100000000L, 1000000000L
|
||
};
|
||
s32 int_part = 0, frac_part = 0;
|
||
int frac_digits = 0, neg = 0;
|
||
s32 result;
|
||
|
||
if (!s || !*s) return 0;
|
||
|
||
while (*s == ' ' || *s == '\t') s++; /* skip whitespace */
|
||
if (*s == '-') { neg = 1; s++; } /* sign */
|
||
else if (*s == '+') { s++; }
|
||
|
||
while (*s >= '0' && *s <= '9') /* integer part */
|
||
{ int_part = int_part * 10 + (*s - '0'); s++; }
|
||
|
||
if (*s == '.') { /* fractional part */
|
||
s++;
|
||
while (*s >= '0' && *s <= '9') {
|
||
if (frac_digits < 9)
|
||
{ frac_part = frac_part * 10 + (*s - '0'); frac_digits++; }
|
||
s++;
|
||
}
|
||
}
|
||
|
||
result = int_part << radix;
|
||
if (frac_digits > 0)
|
||
result += (s32)(((int64_t)frac_part << radix) / pow10[frac_digits]);
|
||
|
||
return neg ? -result : result;
|
||
}
|
||
#endif /* FR_NO_PRINT */
|
||
|
||
/*=======================================================
|
||
* Square root and hypot
|
||
*
|
||
* fr_isqrt64 is a private helper implementing the digit-by-digit
|
||
* ("shift-and-subtract") integer square root. The core loop computes
|
||
* floor(sqrt(n)), then a final remainder check rounds to nearest.
|
||
* Uses no division. At most 32 iterations.
|
||
*/
|
||
static u32 fr_isqrt64(uint64_t n)
|
||
{
|
||
uint64_t root = 0;
|
||
uint64_t bit = (uint64_t)1 << 62;
|
||
while (bit > n) bit >>= 2;
|
||
while (bit != 0)
|
||
{
|
||
uint64_t trial = root + bit;
|
||
if (n >= trial)
|
||
{
|
||
n -= trial;
|
||
root = (root >> 1) + bit;
|
||
}
|
||
else
|
||
{
|
||
root >>= 1;
|
||
}
|
||
bit >>= 2;
|
||
}
|
||
/* round to nearest: if remainder > root, (root+1)^2 is closer */
|
||
if (n > root)
|
||
root++;
|
||
return (u32)root;
|
||
}
|
||
|
||
/*=======================================================
|
||
* FR_sqrt - fixed-radix square root.
|
||
*
|
||
* input : value at radix `radix`. Must be >= 0.
|
||
* radix : fractional bits of input AND result.
|
||
* return : sqrt(input) at radix `radix`, or FR_DOMAIN_ERROR if input < 0.
|
||
*
|
||
* Math: sqrt(input_fp / 2^r) at radix r is
|
||
* result_fp = sqrt(input_fp / 2^r) * 2^r = sqrt(input_fp * 2^r)
|
||
* so we compute isqrt(input_fp << radix) on a 64-bit accumulator. This
|
||
* works for any input that fits in s32 and any radix in [0, 30].
|
||
*
|
||
* Precision: round-to-nearest sqrt. Worst-case absolute error is
|
||
* <= 0.5 LSB at the requested radix.
|
||
* Always non-negative for non-negative input. Result is monotone in
|
||
* input.
|
||
*
|
||
* Saturation: input < 0 returns FR_DOMAIN_ERROR (= INT32_MIN). Caller
|
||
* can test `result == FR_DOMAIN_ERROR` to detect domain errors.
|
||
*
|
||
* Side effects: none. Pure function.
|
||
*/
|
||
s32 FR_sqrt(s32 input, u16 radix)
|
||
{
|
||
uint64_t n;
|
||
|
||
if (input < 0)
|
||
return FR_DOMAIN_ERROR;
|
||
if (input == 0)
|
||
return 0;
|
||
|
||
n = (uint64_t)(u32)input << radix;
|
||
return (s32)fr_isqrt64(n);
|
||
}
|
||
|
||
/*=======================================================
|
||
* FR_hypot - sqrt(x*x + y*y) without intermediate overflow.
|
||
*
|
||
* x, y : values at radix `radix`
|
||
* radix : fractional bits of inputs AND result
|
||
* return : sqrt(x*x + y*y) at radix `radix`.
|
||
*
|
||
* Math: x*x + y*y is naturally at radix 2*radix; isqrt of a 2r-radix
|
||
* value yields an r-radix result, so no extra shifting is needed. The
|
||
* u64 accumulator can hold (INT32_MAX^2)*2 = ~2^63, so (x*x + y*y) never
|
||
* overflows for any s32 inputs.
|
||
*
|
||
* Precision: round-to-nearest. Worst-case absolute error <= 0.5 LSB
|
||
* at the requested radix.
|
||
*
|
||
* Side effects: none. Pure function.
|
||
*/
|
||
#ifndef FR_LEAN
|
||
s32 FR_hypot(s32 x, s32 y, u16 radix)
|
||
{
|
||
uint64_t xx = (uint64_t)((int64_t)x * (int64_t)x);
|
||
uint64_t yy = (uint64_t)((int64_t)y * (int64_t)y);
|
||
(void)radix; /* the 2*radix in xx+yy cancels with isqrt's halving */
|
||
return (s32)fr_isqrt64(xx + yy);
|
||
}
|
||
#endif
|
||
|
||
/*=======================================================
|
||
* FR_hypot_fast8 — 8-segment piecewise-linear magnitude approximation.
|
||
*
|
||
* Shift-only, no multiply, no 64-bit. Based on the piecewise-linear
|
||
* method described in US Patent 6,567,777 B1 (Chatterjee, expired).
|
||
* Peak error: ~0.10%.
|
||
*/
|
||
s32 FR_hypot_fast8(s32 x, s32 y)
|
||
{
|
||
s32 hi, lo;
|
||
|
||
/* absolute values (clamp INT32_MIN to INT32_MAX to avoid UB) */
|
||
if (x < 0) x = (x == (s32)0x80000000) ? 0x7FFFFFFF : -x;
|
||
if (y < 0) y = (y == (s32)0x80000000) ? 0x7FFFFFFF : -y;
|
||
|
||
/* hi = max(|x|,|y|), lo = min(|x|,|y|) */
|
||
if (x > y) { hi = x; lo = y; }
|
||
else { hi = y; lo = x; }
|
||
|
||
if (hi == 0) return 0;
|
||
|
||
/* 8 piecewise-linear segments: dist ≈ a*hi + b*lo.
|
||
* Boundaries at β = 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875. */
|
||
if ((hi >> 1) < lo) {
|
||
/* β in (0.5, 1.0] */
|
||
if (lo > hi - (hi >> 2)) {
|
||
/* β in (0.75, 1.0] */
|
||
if (lo > hi - (hi >> 3)) /* β > 0.875 */
|
||
/* a≈0.7305, b≈0.6836 */
|
||
return hi - (hi >> 2) - (hi >> 6) - (hi >> 8)
|
||
+ lo - (lo >> 2) - (lo >> 4) - (lo >> 8);
|
||
else /* β in (0.75, 0.875] */
|
||
/* a≈0.7803, b≈0.6262 */
|
||
return hi - (hi >> 2) + (hi >> 5) - (hi >> 10)
|
||
+ (lo >> 1) + (lo >> 3) + (lo >> 10) + (lo >> 12);
|
||
} else {
|
||
/* β in (0.5, 0.75] */
|
||
if (lo > hi - (hi >> 1) + (hi >> 3)) /* β > 0.625 */
|
||
/* a≈0.8281, b≈0.5630 */
|
||
return hi - (hi >> 2) + (hi >> 4) + (hi >> 6)
|
||
+ (lo >> 1) + (lo >> 4) + (lo >> 11);
|
||
else /* β in (0.5, 0.625] */
|
||
/* a≈0.8728, b≈0.4893 */
|
||
return hi - (hi >> 3) - (hi >> 9) - (hi >> 12)
|
||
+ (lo >> 1) - (lo >> 6) + (lo >> 8) + (lo >> 10);
|
||
}
|
||
} else {
|
||
/* β in [0, 0.5] */
|
||
if ((hi >> 2) < lo) {
|
||
/* β in (0.25, 0.5] */
|
||
if ((hi >> 1) - (hi >> 3) < lo) /* β > 0.375 */
|
||
/* a≈0.9180, b≈0.3984 */
|
||
return hi - (hi >> 4) - (hi >> 6) - (hi >> 8)
|
||
+ (lo >> 1) - (lo >> 3) + (lo >> 5) - (lo >> 7);
|
||
else /* β in (0.25, 0.375] */
|
||
/* a≈0.9551, b≈0.2988 */
|
||
return hi - (hi >> 4) + (hi >> 6) + (hi >> 9)
|
||
+ (lo >> 2) + (lo >> 4) - (lo >> 6) + (lo >> 9);
|
||
} else {
|
||
/* β in [0, 0.25] */
|
||
if ((hi >> 3) < lo) /* β in (0.125, 0.25] */
|
||
/* a≈0.9839, b≈0.1838 */
|
||
return hi - (hi >> 6) - (hi >> 11)
|
||
+ (lo >> 2) - (lo >> 4) - (lo >> 8) + (lo >> 12);
|
||
else /* β in [0, 0.125] */
|
||
/* a≈0.9990, b≈0.0620 */
|
||
return hi - (hi >> 10)
|
||
+ (lo >> 4) - (lo >> 11);
|
||
}
|
||
}
|
||
}
|
||
|
||
#ifndef FR_NO_WAVES
|
||
/*=======================================================
|
||
* Wave generators — synth-style fixed-shape waveforms.
|
||
*
|
||
* All wave functions take a u16 BAM phase in [0, 65535] (a full cycle)
|
||
* and return s16 in s0.15 format, clamped to [-32767, +32767] to match
|
||
* the trig amplitude convention used by fr_cos_bam / fr_sin_bam.
|
||
*
|
||
* Use FR_HZ2BAM_INC(hz, sample_rate) to compute a phase increment for
|
||
* a given output frequency, then accumulate it (mod 2^16) per sample.
|
||
*
|
||
* Side effects: pure functions (except fr_wave_noise which advances a
|
||
* caller-provided LFSR state pointer).
|
||
*/
|
||
|
||
/* fr_wave_sqr - 50%-duty square wave.
|
||
* phase < pi (BAM<0x8000) → +full; phase >= pi → -full.
|
||
*/
|
||
s16 fr_wave_sqr(u16 phase)
|
||
{
|
||
return (phase < 0x8000) ? (s16)32767 : (s16)-32767;
|
||
}
|
||
|
||
/* fr_wave_pwm - variable-duty pulse.
|
||
* `duty` is the BAM threshold: phase < duty → high, else low.
|
||
* duty = 0 → always low
|
||
* duty = 0x8000 → 50% duty (same as fr_wave_sqr)
|
||
* duty = 0xffff → high almost everywhere (one BAM step low)
|
||
*/
|
||
s16 fr_wave_pwm(u16 phase, u16 duty)
|
||
{
|
||
return (phase < duty) ? (s16)32767 : (s16)-32767;
|
||
}
|
||
|
||
/* fr_wave_saw - rising sawtooth.
|
||
* Linear ramp from -32767 (just after phase=0) to +32767 (at phase=0xffff),
|
||
* passing through 0 at phase=0x8000. The single boundary case phase=0
|
||
* (which would naturally produce -32768) is clamped to -32767 to keep the
|
||
* amplitude symmetric.
|
||
*/
|
||
s16 fr_wave_saw(u16 phase)
|
||
{
|
||
s32 v = (s32)phase - (s32)0x8000;
|
||
if (v < -32767) v = -32767;
|
||
return (s16)v;
|
||
}
|
||
|
||
/* fr_wave_tri - symmetric triangle.
|
||
* Four linear segments:
|
||
* Q1 [0, 0x4000) : rising 0 → +peak
|
||
* Q2 [0x4000, 0x8000): falling +peak → 0
|
||
* Q3 [0x8000, 0xc000): falling 0 → -peak
|
||
* Q4 [0xc000, 0x10000): rising -peak → 0
|
||
* Peaks are clamped to +/-32767 (the natural unclamped formula gives
|
||
* +/-32768 at the exact peak BAM).
|
||
*/
|
||
s16 fr_wave_tri(u16 phase)
|
||
{
|
||
s32 t;
|
||
if (phase < 0x8000)
|
||
{
|
||
/* First half: 0 -> +peak -> 0 */
|
||
if (phase < 0x4000)
|
||
t = (s32)phase << 1; /* 0 .. 0x7ffe */
|
||
else
|
||
t = (s32)(0x8000 - phase) << 1; /* 0x8000 .. 2 */
|
||
if (t > 32767) t = 32767;
|
||
return (s16)t;
|
||
}
|
||
else
|
||
{
|
||
/* Second half: 0 -> -peak -> 0 */
|
||
if (phase < 0xc000)
|
||
t = (s32)(phase - 0x8000) << 1; /* 0 .. 0x7ffe */
|
||
else
|
||
t = (s32)(0x10000 - phase) << 1;/* 0x8000 .. 2 */
|
||
if (t > 32767) t = 32767;
|
||
return (s16)-t;
|
||
}
|
||
}
|
||
|
||
/* fr_wave_tri_morph - variable-symmetry triangle.
|
||
*
|
||
* phase : u16 BAM
|
||
* break_point : u16 BAM where the wave reaches its positive peak.
|
||
*
|
||
* Going from 0 to +peak in [0, break_point), then from +peak back to 0
|
||
* in [break_point, 0xffff]. The result is a triangle whose rising and
|
||
* falling slopes can differ.
|
||
*
|
||
* break_point = 0x8000 → symmetric triangle
|
||
* break_point = 0xffff → rising sawtooth (instant fall)
|
||
* break_point = 0x0001 → falling sawtooth (instant rise)
|
||
* break_point = 0 → degenerate; treated as 1 to avoid div-by-zero
|
||
*
|
||
* Note that this version returns values in [0, 32767] only (not bipolar).
|
||
* Caller can subtract 16384 and double if a bipolar version is desired.
|
||
*
|
||
* Costs: one 32-bit divide per sample. On Cortex-M3+ this is ~10-20
|
||
* cycles. On 8051 / MSP430 this is much slower; pre-compute slopes if
|
||
* those targets matter to you.
|
||
*/
|
||
s16 fr_wave_tri_morph(u16 phase, u16 break_point)
|
||
{
|
||
u32 t;
|
||
if (break_point == 0)
|
||
break_point = 1;
|
||
if (phase < break_point)
|
||
{
|
||
/* rising: 0 at phase=0, 32767 at phase=break_point */
|
||
t = (u32)(((u32)phase * 32767UL) / (u32)break_point);
|
||
}
|
||
else
|
||
{
|
||
/* falling: 32767 at phase=break_point, 0 at phase=0xffff */
|
||
u32 span = (u32)0xffff - (u32)break_point;
|
||
if (span == 0)
|
||
return 32767;
|
||
t = (u32)(((u32)((u32)0xffff - (u32)phase) * 32767UL) / span);
|
||
}
|
||
if (t > 32767) t = 32767;
|
||
return (s16)t;
|
||
}
|
||
|
||
/* fr_wave_noise - LFSR-based pseudorandom noise.
|
||
*
|
||
* state : pointer to a u32 the caller maintains. Initial value must
|
||
* be non-zero (zero is a fixed point of the LFSR). A common
|
||
* seed is 0xACE1u or any other non-zero constant.
|
||
*
|
||
* Returns the next s16 sample in s0.15 (full ±32767 range, white-ish).
|
||
* Implementation: 32-bit Galois LFSR with the standard maximal-period
|
||
* tap polynomial 0xD0000001 (period 2^32 - 1 samples).
|
||
*
|
||
* Quality: this is "fast white noise" suitable for synth use. It is NOT
|
||
* cryptographically secure. For better statistical properties (FFT
|
||
* flatness etc.) layer a longer LFSR or use a separate PRNG.
|
||
*/
|
||
s16 fr_wave_noise(u32 *state)
|
||
{
|
||
u32 lsb;
|
||
if (!state)
|
||
return 0;
|
||
lsb = *state & 1u;
|
||
*state >>= 1;
|
||
if (lsb)
|
||
*state ^= 0xD0000001u;
|
||
/* Take the top 16 bits and re-bias to s16 range, clamp to ±32767. */
|
||
{
|
||
s32 v = (s32)((*state >> 16) & 0xffffu) - 32768;
|
||
if (v < -32767) v = -32767;
|
||
return (s16)v;
|
||
}
|
||
}
|
||
|
||
/*=======================================================
|
||
* ADSR envelope generator
|
||
*
|
||
* Linear-segment Attack-Decay-Sustain-Release envelope. State is held
|
||
* in caller-allocated fr_adsr_t struct (no global state, no malloc).
|
||
*
|
||
* Lifecycle:
|
||
* 1. Caller allocates an fr_adsr_t (stack or static).
|
||
* 2. fr_adsr_init() once per patch with attack/decay/release durations
|
||
* in samples and a sustain level in s0.15.
|
||
* 3. fr_adsr_trigger() on note-on. Output rises 0 -> peak over `atk`
|
||
* samples, falls peak -> sustain over `dec` samples, then holds.
|
||
* 4. fr_adsr_release() on note-off. Output falls current -> 0 over a
|
||
* time controlled by the release rate (rate, not duration: the
|
||
* time depends on where in the envelope we are).
|
||
* 5. fr_adsr_step() once per audio sample to read the current value.
|
||
*
|
||
* Internal precision: levels are stored as s32 in s1.30 format so even
|
||
* very long envelopes (e.g. 48000-sample attack at 48 kHz = 1 second)
|
||
* have a non-zero per-sample increment. Output is converted to s0.15.
|
||
*
|
||
* Saturation: the envelope state machine is self-clamping; level cannot
|
||
* escape [0, 1<<30]. Output is in [0, 32767].
|
||
*/
|
||
|
||
#define FR_ADSR_PEAK_S130 ((s32)1 << 30)
|
||
|
||
void fr_adsr_init(fr_adsr_t *env,
|
||
u32 attack_samples,
|
||
u32 decay_samples,
|
||
s16 sustain_level_s015,
|
||
u32 release_samples)
|
||
{
|
||
if (!env)
|
||
return;
|
||
env->state = FR_ADSR_IDLE;
|
||
env->level = 0;
|
||
|
||
/* sustain_level_s015 is s16 so its upper bound (32767) is already the
|
||
* type's max; only the lower bound needs an explicit clamp. */
|
||
if (sustain_level_s015 < 0)
|
||
sustain_level_s015 = 0;
|
||
/* Convert s0.15 -> s1.30 by shifting left 15. */
|
||
env->sustain = (s32)sustain_level_s015 << 15;
|
||
|
||
env->attack_inc = (attack_samples > 0)
|
||
? (s32)(FR_ADSR_PEAK_S130 / attack_samples)
|
||
: FR_ADSR_PEAK_S130;
|
||
env->decay_dec = (decay_samples > 0)
|
||
? (s32)((FR_ADSR_PEAK_S130 - env->sustain) / (s32)decay_samples)
|
||
: (FR_ADSR_PEAK_S130 - env->sustain);
|
||
env->release_dec = (release_samples > 0)
|
||
? (s32)(FR_ADSR_PEAK_S130 / release_samples)
|
||
: FR_ADSR_PEAK_S130;
|
||
}
|
||
|
||
void fr_adsr_trigger(fr_adsr_t *env)
|
||
{
|
||
if (!env)
|
||
return;
|
||
env->state = FR_ADSR_ATTACK;
|
||
env->level = 0;
|
||
}
|
||
|
||
void fr_adsr_release(fr_adsr_t *env)
|
||
{
|
||
if (!env)
|
||
return;
|
||
env->state = FR_ADSR_RELEASE;
|
||
}
|
||
|
||
s16 fr_adsr_step(fr_adsr_t *env)
|
||
{
|
||
if (!env)
|
||
return 0;
|
||
switch (env->state)
|
||
{
|
||
case FR_ADSR_ATTACK:
|
||
env->level += env->attack_inc;
|
||
if (env->level >= FR_ADSR_PEAK_S130)
|
||
{
|
||
env->level = FR_ADSR_PEAK_S130;
|
||
env->state = FR_ADSR_DECAY;
|
||
}
|
||
break;
|
||
case FR_ADSR_DECAY:
|
||
env->level -= env->decay_dec;
|
||
if (env->level <= env->sustain)
|
||
{
|
||
env->level = env->sustain;
|
||
env->state = FR_ADSR_SUSTAIN;
|
||
}
|
||
break;
|
||
case FR_ADSR_SUSTAIN:
|
||
env->level = env->sustain;
|
||
break;
|
||
case FR_ADSR_RELEASE:
|
||
env->level -= env->release_dec;
|
||
if (env->level <= 0)
|
||
{
|
||
env->level = 0;
|
||
env->state = FR_ADSR_IDLE;
|
||
}
|
||
break;
|
||
case FR_ADSR_IDLE:
|
||
default:
|
||
env->level = 0;
|
||
break;
|
||
}
|
||
/* s1.30 -> s0.15: shift right 15. Clamp for safety. */
|
||
{
|
||
s32 out = env->level >> 15;
|
||
if (out < 0) out = 0;
|
||
if (out > 32767) out = 32767;
|
||
return (s16)out;
|
||
}
|
||
}
|
||
#endif /* FR_NO_WAVES */
|