/** * @FR_math.h - header definition file for fixed radix math routines * * @copy Copyright (C) <2001-2026> * @author M A Chatterjee * * This file contains integer math settable fixed point radix math routines for * use on systems in which floating point is not desired or unavailable. * * @license: * 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, an acknowledgment in the product documentation would be * appreciated but is not required. * * 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. * */ #ifndef __FR_Math_h__ #define __FR_Math_h__ #define FR_MATH_VERSION "2.0.8" #define FR_MATH_VERSION_HEX 0x020008 /* major << 16 | minor << 8 | patch */ #ifdef FR_CORE_ONLY #define FR_NO_PRINT #define FR_NO_WAVES #endif #ifdef FR_LEAN #define FR_NO_WAVES #endif #ifdef __cplusplus extern "C" { #endif #ifndef __FR_Platform_Defs_H__ #include "FR_defs.h" #endif /* Quick note on macro parameter wrapping: * Arguments are parenthesized in expansions, e.g. * #define MACRO_X_SQUARED(x) ((x)*(x)) // inner parens around each x * Macros substitute text as-is. If a parameter is an expression like 3+4*5 * and the body mixes operators without extra parentheses, precedence errors * follow. Parenthesize parameters (and fragile subexpressions) in the macro body. * Example: MACRO_X_SQUARED_BAD(x) (x*x) -> 3+4*5*3+4*5 == 83 (wrong). * MACRO_X_SQUARED(x) ((x)*(x)) -> (3+4*5)*(3+4*5) == 529 (right). */ /*absolute value for integer and fixed radix types*/ #define FR_ABS(x) (((x) < 0) ? (-(x)) : (x)) /*sign of x. Not as good as The Sign of Four, but I digress */ #define FR_SGN(x) ((x) >> ((((signed)sizeof(x)) << 3) - 1)) /*=============================================== * Simple Fixed Point Math Conversions * r is radix precision in bits, converts to/from integer w truncation */ #define I2FR(x, r) ((x) << (r)) #define FR2I(x, r) ((x) >> (r)) /*=============================================== * Make a fixed radix number from integer + base-10 fractional parts. * r = output radix (fractional bits) * i = integer part (signed) * f = decimal-fraction digits as written, e.g. for "12.34" pass f=34 * d = number of decimal digits in f, e.g. for "12.34" pass d=2 * * FR_NUM(12, 34, 2, 10) ≈ 12.34 in s.10 = (12<<10) + (34<<10)/100 = 12636 * FR_NUM(-3, 5, 1, 16) ≈ -3.5 in s.16 = (-3<<16) - (5<<16)/10 * * The fraction is rounded toward zero. For round-to-nearest, add half an LSB * before scaling at the call site. Sign of the fractional part follows the * sign of i (for i==0 the result is positive, matching "+0.5" intuition). */ #define FR_NUM_POW10(d) ( \ ((d) == 0) ? 1L : \ ((d) == 1) ? 10L : \ ((d) == 2) ? 100L : \ ((d) == 3) ? 1000L : \ ((d) == 4) ? 10000L : \ ((d) == 5) ? 100000L : \ ((d) == 6) ? 1000000L : \ ((d) == 7) ? 10000000L : \ ((d) == 8) ? 100000000L : 1000000000L) #define FR_NUM(i, f, d, r) ( \ ((s32)(i) << (r)) + \ (((i) < 0) \ ? -((s32)(((s32)(f) << (r)) / FR_NUM_POW10(d))) \ : ((s32)(((s32)(f) << (r)) / FR_NUM_POW10(d))))) /* FR_INT(x,r) convert a fixed radix variable x of radix r to an integer */ #define FR_INT(x, r) (((x) < 0) ? -((-(x)) >> (r)) : ((x) >> (r))) /* Change Radix (x,current_radix, new_radix) * change number from its current fixed radix (can be 0 or integer) to a new fixed radix * Useful when dealing with numbers with mixed radixes. This is a MACRO so * this code expands in place and x is modified. */ #define FR_CHRDX(x, r_cur, r_new) (((r_cur) - (r_new)) >= 0 ? ((x) >> ((r_cur) - (r_new))) : ((x) << ((r_new) - (r_cur)))) /* return only the fractional part of x */ #define FR_FRAC(x, r) ((FR_ABS(x)) & (((1 << (r)) - 1))) /* return the fractional part of number x with radix xr scaled to radix nr bits */ #define FR_FRACS(x, xr, nr) (FR_CHRDX(FR_FRAC((x), (xr)), (xr), (nr))) /****************************************************** Add (sub) to fixed point numbers by converting the second number to the same radix as the first. If yr < xr then possibility of overflow is increased. Note: for two vars, i, j, of prec ir, jr, it is not necessarily true that FR_ADD(i,ir,j,jr) == FR_ADD(j,jr,i,ir) */ #define FR_ADD(x, xr, y, yr) ((x) += FR_CHRDX(y, yr, xr)) #define FR_SUB(x, xr, y, yr) ((x) -= FR_CHRDX(y, yr, xr)) /* Fixed-radix division with round-to-nearest: x (at radix xr) / y (at radix yr), * result at radix xr. Uses a 64-bit intermediate so the full Q16.16 range works * correctly. Adds half the divisor before truncation to achieve ≤ 0.5 LSB error. */ static inline s32 FR_div_rnd(s64 num, s32 den) { if ((num ^ den) >= 0) /* same sign: positive quotient */ return (s32)((num + den / 2) / den); else /* negative quotient */ return (s32)((num - den / 2) / den); } #define FR_DIV(x, xr, y, yr) FR_div_rnd((s64)(x) << (yr), (s32)(y)) /* FR_DIV_TRUNC: truncating division (old FR_DIV behavior). Useful when the * caller knows the quotient is exact or truncation is acceptable. */ #define FR_DIV_TRUNC(x, xr, y, yr) ((s32)(((s64)(x) << (yr)) / (s32)(y))) /* FR_DIV32: 32-bit-only division. Requires |x| < 2^(31-yr) to avoid * overflow in the intermediate (x << yr). Use FR_DIV for full-range * division with 64-bit intermediate. */ #define FR_DIV32(x, xr, y, yr) (((s32)(x) << (yr)) / (s32)(y)) /* Remainder: both operands should be at the same radix. */ #define FR_MOD(x, y) ((x) % (y)) /* min, max, clamp */ #define FR_MIN(a, b) (((a) < (b)) ? (a) : (b)) #define FR_MAX(a, b) (((a) > (b)) ? (a) : (b)) #define FR_CLAMP(x, lo, hi) (FR_MIN(FR_MAX((x), (lo)), (hi))) /* Check if x is a power of 2. */ #define FR_ISPOW2(x) (!((x) & ((x) - 1))) /* floor and ceiling in current radix, leaving current radix intact * this means the lower radix number of bits are set to 0 */ #define FR_FLOOR(x, r) ((x) & (~((1 << r) - 1))) #define FR_CEIL(x, r) (FR_FLOOR(x, r) + ((FR_FRAC(x, r) ? (1 << r) : 0))) /******************************************************* Interpolate between 2 fixed point values (x0,x1) of any radix, with a fractional delta, of a supplied precision. If delta is outside 0<= delta<=1 then extrapolation does work but programmer should watch for possible overflow. x0,x1 need not have same radix as delta */ #define FR_INTERP(x0, x1, delta, prec) ((x0) + ((((x1) - (x0)) * (delta)) >> (prec))) /****************************************************** FR_INTERPI is the same as FR_INTERP except that insures the range is btw [0<= delta < 1] --> ((0 ... (1<= 0 */ #define FR_INTERPI(x0, x1, delta, prec) ((x0) + ((((x1) - (x0)) * ((delta) & ((1 << (prec)) - 1))) >> (prec))) /****************************************************** Convert to double, this is for debug only and WILL NOT compile under many embedded systems. Fixed Radix to Floating Point Double Conversion since this is a MACRO it will not be compiled or instantiated unless it is actually called in code. */ #define FR2D(x, r) ((double)(((double)(x)) / ((double)(1 << (r))))) #define D2FR(d, r) ((s32)(d * (1 << r))) /****************************************************** Useful Constants FR_kXXXX "k" denotes constant to help when reading macros used in source code FR_krXXX "r" == reciprocal e.g. 1/XXX As these are MACROS, they take only compiled code space if actually used. Consts here calculated by multiply the natural base10 value by (1<> 28)) // common sqrts #define FR_kSQRT2 (92682) /* 1.414213562373 */ #define FR_krSQRT2 (46341) /* 0.707106781186 */ #define FR_kSQRT3 (113512) /* 1.732050807568 */ #define FR_krSQRT3 (37837) /* 0.577350269189 */ #define FR_kSQRT5 (146543) /* 2.236067977599 */ #define FR_krSQRT5 (29309) /* 0.447213595499 */ #define FR_kSQRT10 (207243) /* 3.162277660168 */ #define FR_krSQRT10 (20724) /* 0.316227766016 */ /*=============================================== * Arithmetic operations */ s32 FR_FixMuls(s32 x, s32 y); // mul signed, round-to-nearest, NOT saturated s32 FR_FixMulSat(s32 x, s32 y); // mul signed, round-to-nearest, saturated s32 FR_FixAddSat(s32 x, s32 y); // add signed, saturated /*================================================ * Constants used in Trig tables, definitions * * FR_TRIG_PREC — internal table precision (u0.15, sine table) * FR_TRIG_OUT_PREC — output precision of sin/cos/tan (s15.16 since v2.0.1) * FR_TRIG_ONE — exact 1.0 in output format (1 << 16 = 65536) * * sin/cos return s32 at radix 16 (s15.16). This matches libfixmath Q16.16 * precision and allows exact representation of 1.0 at the poles. * tan returns s32 at radix 16 (s15.16). Saturates at ±FR_TRIG_MAXVAL. */ #define FR_TRIG_PREC (15) #define FR_TRIG_OUT_PREC (16) #define FR_TRIG_MASK ((1 << (FR_TRIG_PREC)) - 1) #define FR_TRIG_ONE (1L << FR_TRIG_OUT_PREC) /* 65536 = 1.0 */ #define FR_TRIG_MAXVAL ((s32)0x7fffffff) /* tan saturation max */ #define FR_TRIG_MINVAL (-FR_TRIG_MAXVAL) /* tan saturation min */ /* Bit Shift Scaling macros. Useful on some platforms with poor MUL performance. * Also can be useful if you need to scale numbers with * large portions of bits_in_use and a larger register size is not available. * For example, suppose you need to scale a large 32bit number say z=0xa4239323 * from degrees to radians. Ideally this number would be multiplied by 0.017 * but using a (z*FR_kDEG2RAD) >> FR_kPREC type operation is likely to overflow * due the MUL result being large. The FR_RAD2DEG MACRO below doesn't require * accumulator headroom bits and so has no chance for loss of precision. * Another benefit is some low end micros (8051, 68xx, MSP430(low end), * PIC-8 family) have no multiplier. So these allow the programmer an option * to see if performance or precision are better expressed as shifts rather than * scaled multiplies. As always, mileage may vary depending on architecture, * compiler and other considerations. */ /* scale by 10s */ #define FR_SMUL10(x) (((x) << 3) + (((x) << 1))) #define FR_SDIV10(x) (((x) >> 3) - ((x) >> 5) + ((x) >> 7) - ((x) >> 9) + ((x) >> 11)) /* scale by 1/log2(e) 0.693147180560 used for converting log2() to ln() */ #define FR_SrLOG2E(x) (((x) >> 1) + ((x) >> 2) - ((x) >> 3) + ((x) >> 4) + ((x) >> 7) - ((x) >> 9) - ((x) >> 12) + ((x) >> 15)) /* scale by log2(e) 1.442695040889 used for converting pow2() to exp() */ #define FR_SLOG2E(x) ((x) + ((x) >> 1) - ((x) >> 4) + ((x) >> 8) + ((x) >> 10) + ((x) >> 12) + ((x) >> 14)) /* scale by 1/log2(10) 0.30102999566 used for converting log2() to log10 */ #define FR_SrLOG2_10(x) (((x) >> 2) + ((x) >> 4) - ((x) >> 6) + ((x) >> 7) - ((x) >> 8) + ((x) >> 12)) /* scale by log2(10) 3.32192809489 used for converting pow2() to pow10 */ #define FR_SLOG2_10(x) (((x) << 1) + (x) + ((x) >> 2) + ((x) >> 4) + ((x) >> 7) + ((x) >> 10) + ((x) >> 11) + ((x) >> 13)) /* Shift-only angular conversion macros * * All are pure constant multipliers expressed as shifts — no multiply, no * divide, no 64-bit intermediates, no accumulators. Work at any radix: if * your input is degrees at radix 8, the output is the target unit at radix 8. * The caller shifts as needed. * * Angular units: * degrees = 360 per revolution * radians = 2*pi per revolution * BAM = 65536 per revolution (Binary Angular Measure, u16) * quadrants = 4 per revolution (= BAM >> 14) * * Side-effect note: x is referenced multiple times in each macro — do not * pass expressions with side effects. */ /* FR_DEG2RAD(x): multiply by pi/180 ≈ 0.017453 (5 terms, ~17 bits) */ #define FR_DEG2RAD(x) (((x) >> 6) + ((x) >> 9) - ((x) >> 13) - ((x) >> 19) - ((x) >> 20)) /* FR_RAD2DEG(x): multiply by 180/pi ≈ 57.29578 (7 terms, ~19 bits) */ #define FR_RAD2DEG(x) (((x) << 6) - ((x) << 3) + (x) + ((x) >> 2) + (((x) >> 4) - ((x) >> 6)) - ((x) >> 10)) /* FR_DEG2BAM(x): multiply by 65536/360 ≈ 182.0449 (7 terms, ~18 bits). * Intermediate terms overflow s32 when |x| > ~256 deg at s15.16 (x<<7 term), * but the overflow is harmless when the result is truncated to u16 BAM * (two's complement wrapping preserves modular correctness). * For full-precision s32 BAM (sub-BAM interpolation), use fr_deg_to_bam(). */ #define FR_DEG2BAM(x) (((x)<<7)+((x)<<6)-((x)<<3)-((x)<<1)+((x)>>5)+((x)>>6)-((x)>>9)) /* FR_BAM2DEG(x): multiply by 360/65536 = 0.00549316 (4 terms, exact) */ #define FR_BAM2DEG(x) (((x)>>8)+((x)>>9)-((x)>>12)-((x)>>13)) /* FR_RAD2BAM(x): multiply by 65536/(2*pi) ≈ 10430.378 (7 terms, ~21 bits). * CAUTION: overflows s32 when |x| > ~4 rad at s15.16 (x<<13 term). * For safe conversion at any radix, use fr_rad_to_bam() instead. * #define FR_RAD2BAM(x) (((x)<<13)+((x)<<11)+((x)<<7)+((x)<<6)-((x)<<1)+((x)>>1)-((x)>>3)) */ #define FR_RAD2BAM(x) (((x)<<13)+((x)<<11)+((x)<<7)+((x)<<6)-((x)<<1)+((x)>>1)-((x)>>3)+((x)>>8)-((x)>>11)-((x)>>14)) /* ── Overflow-safe rad/deg to BAM conversion functions ───────────── * * These replace the FR_RAD2BAM / FR_DEG2BAM macros for callers that * need the full ±2*pi or ±360° range at any radix. * * Strategy: normalize input to radix 16, conditionally reduce into * a safe zone, apply the full-precision shift-only multiply, then * extract the u16 BAM. No precision loss from halving/quartering. * * fr_rad_to_bam: reduce to [-pi, pi], reordered terms. ±2*pi safe. * fr_deg_to_bam: reduce to [-90, 90) + quadrant offset. ±360° safe. */ /* Pi constants at any radix: FR_PI(r) = round(pi * 2^r), etc. * Compiler evaluates at compile time when r is a constant. * Max safe radix: FR_PI r<=29, FR_TWO_PI r<=28, FR_HALF_PI r<=30. */ #define FR_PI(r) ((s32)(3.14159265358979323846 * (1LL << (r)) + 0.5)) #define FR_TWO_PI(r) ((s32)(6.28318530717958647692 * (1LL << (r)) + 0.5)) #define FR_HALF_PI(r) ((s32)(1.57079632679489661923 * (1LL << (r)) + 0.5)) #define FR_THREE_HALF_PI(r) ((s32)(4.71238898038468985769 * (1LL << (r)) + 0.5)) /* Convenience aliases at radix 16 */ #define FR_PI_R16 FR_PI(16) #define FR_TWO_PI_R16 FR_TWO_PI(16) /* Degree constants at radix 16 (exact — no truncation) */ #define FR_D90_R16 ((s32)90 << 16) #define FR_D180_R16 ((s32)180 << 16) #define FR_D360_R16 ((s32)360 << 16) u16 fr_rad_to_bam(s32 rad, u16 radix); #ifndef FR_LEAN u16 fr_deg_to_bam(s32 deg, u16 radix); #endif /* FR_BAM2RAD(x): multiply by 2*pi/65536 ≈ 0.0000959 (5 terms, ~18 bits) */ #define FR_BAM2RAD(x) (((x)>>13)-((x)>>15)+((x)>>18)+((x)>>21)+((x)>>25)) /* Legacy quadrant macros (quadrants = BAM >> 14) */ #define FR_RAD2Q(x) (((x) >> 1) + ((x) >> 3) + ((x) >> 7) + ((x) >> 8) - ((x) >> 14)) #define FR_Q2RAD(x) ((x) + ((x) >> 1) + ((x) >> 4) + ((x) >> 7) + ((x) >> 11)) #define FR_DEG2Q(x) (((x) >> 6) - ((x) >> 8) - ((x) >> 11) - ((x) >> 13)) #define FR_Q2DEG(x) (((x) << 6) + ((x) << 4) + ((x) << 3) + ((x) << 1)) /*=============================================== * BAM (Binary Angular Measure) — internal angle representation * * One full circle = 2^16 BAM units. So: * 0 = 0 deg = 0 rad * 16384 = 90 deg = pi/2 rad (FR_BAM_QUADRANT) * 32768 = 180 deg = pi rad * 49152 = 270 deg = 3pi/2 rad * 65536 wraps to 0 (because BAM is u16) * * BAM is the natural representation for fixed-point trig because: * - The top 2 bits select the quadrant (no `% 360` modulo needed). * - The next 7 bits index the 128-entry quadrant table directly. * - The bottom 7 bits give linear-interpolation precision. */ #define FR_BAM_BITS (16) #define FR_BAM_FULL (1L << FR_BAM_BITS) /* 65536 */ #define FR_BAM_QUADRANT (FR_BAM_FULL >> 2) /* 16384 */ #define FR_BAM_HALF (FR_BAM_FULL >> 1) /* 32768 */ /*=============================================== * Radian-native and BAM-native trig (recommended) * * All sin/cos functions return s32 at radix 16 (s15.16). * 1.0 is represented exactly as FR_TRIG_ONE (65536). * Poles (0, 90, 180, 270 deg) produce exact ±FR_TRIG_ONE or 0. * * fr_cos_bam(bam) — cos of a BAM angle, s15.16 result * fr_sin_bam(bam) — sin of a BAM angle, s15.16 result * fr_cos(rad, radix) — cos of radians at radix, s15.16 result * fr_sin(rad, radix) — sin of radians at radix, s15.16 result * fr_tan(rad, radix) — tan of radians at radix, s15.16 result * fr_cos_deg(deg, radix) — cos of fixed-radix degrees, s15.16 result * fr_sin_deg(deg, radix) — sin of fixed-radix degrees, s15.16 result * fr_tan_deg(deg, radix) — tan of fixed-radix degrees, s15.16 result * * All go through the same 129-entry quadrant table with linear interpolation. * Worst-case error: ~2 LSB in s15.16 (~3e-5 absolute), except at the four * cardinal angles where the result is exact. * * The radian and degree wrappers (fr_sin, fr_cos, fr_tan, etc.) range-reduce * their input, convert to u16 BAM, and call the BAM-native functions. Small- * angle bypasses at the zero crossings (sin≈0, cos≈0, tan≈0) use the linear * approximation sin(δ)≈δ to avoid BAM quantization error where it matters most. */ s32 fr_cos_bam(u16 bam); s32 fr_sin_bam(u16 bam); #ifndef FR_LEAN s32 fr_tan_bam(u16 bam); #endif s32 fr_cos(s32 rad, u16 radix); s32 fr_sin(s32 rad, u16 radix); s32 fr_tan(s32 rad, u16 radix); /* Integer degrees -> BAM using division (exact at all multiples of 45 deg). */ #define FR_DEG2BAM_I(deg) ((u16)((((s32)(deg) << 16) + ((deg) >= 0 ? 180 : -180)) / 360)) /* Legacy single-arg integer-degree macros — use FR_CosI / FR_SinI instead */ /* #define fr_cos_deg(deg) fr_cos_bam(FR_DEG2BAM_I(deg)) — removed, name reused for 2-arg function */ /* #define fr_sin_deg(deg) fr_sin_bam(FR_DEG2BAM_I(deg)) — removed, name reused for 2-arg function */ #ifndef FR_LEAN /*=============================================== * Degree-input trig API * * FR_CosI(deg) — cos of integer degrees, s15.16 result * FR_SinI(deg) — sin of integer degrees, s15.16 result * FR_TanI(deg) — tan of integer degrees, s15.16 result * fr_cos_deg(deg, radix) — cos of fixed-radix degrees, s15.16 result * fr_sin_deg(deg, radix) — sin of fixed-radix degrees, s15.16 result * fr_tan_deg(deg, radix) — tan of fixed-radix degrees, s15.16 result */ #define FR_CosI(deg) fr_cos_bam(FR_DEG2BAM_I(deg)) #define FR_SinI(deg) fr_sin_bam(FR_DEG2BAM_I(deg)) s32 fr_cos_deg(s32 deg, u16 radix); s32 fr_sin_deg(s32 deg, u16 radix); s32 FR_TanI(s32 deg); s32 fr_tan_deg(s32 deg, u16 radix); /* Legacy macros — use fr_sin_deg/fr_cos_deg/fr_tan_deg in new code */ #define FR_Sin fr_sin_deg #define FR_Cos fr_cos_deg #define FR_Tan fr_tan_deg #endif /* FR_LEAN */ /* Inverse trig — output in radians at caller-specified radix (s32). * FR_atan2 returns radians at radix 16 (s15.16). * Range: acos [0, pi], asin [-pi/2, pi/2], * atan [-pi/2, pi/2], atan2 [-pi, pi]. */ s32 FR_acos(s32 input, u16 radix, u16 out_radix); s32 FR_asin(s32 input, u16 radix, u16 out_radix); s32 FR_atan(s32 input, u16 radix, u16 out_radix); s32 FR_atan2(s32 y, s32 x, u16 out_radix); /* Logarithms */ #define FR_LOG2MIN (-(32767 << 16)) /* returned instead of "negative infinity" */ s32 FR_log2(s32 input, u16 radix, u16 output_radix); s32 FR_ln(s32 input, u16 radix, u16 output_radix); #ifndef FR_LEAN s32 FR_log10(s32 input, u16 radix, u16 output_radix); #endif /* Power */ s32 FR_pow2(s32 input, u16 radix); #define FR_EXP(input, radix) (FR_pow2(FR_MULK28((input), FR_kLOG2E_28), (radix))) #define FR_POW10(input, radix) (FR_pow2(FR_MULK28((input), FR_kLOG2_10_28), (radix))) /* Shift-only (multiply-free) base-conversion variants. * Lower accuracy (~5-10 LSB at Q16.16) but no multiply instruction. * Use these on targets where 32x32->64 multiply is expensive. */ #define FR_EXP_FAST(input, radix) (FR_pow2(FR_SLOG2E(input), radix)) #define FR_POW10_FAST(input, radix) (FR_pow2(FR_SLOG2_10(input), radix)) /*=============================================== * Formatted output and string parsing * * Define FR_NO_PRINT before including this header to exclude all * print/format functions from compilation. This saves ~1.7 KB of ROM * on targets that don't need human-readable output (e.g. headless * sensor nodes, DSP-only firmware). * * #define FR_NO_PRINT * #include "FR_math.h" */ #ifndef FR_NO_PRINT /* printing family of functions */ int FR_printNumF(int (*f)(char), s32 n, int radix, int pad, int prec); /* print fixed radix num as floating point e.g. -12.34" */ int FR_printNumD(int (*f)(char), int n, int pad); /* print decimal number with optional padding e.g. " 12" */ int FR_printNumH(int (*f)(char), int n, int showPrefix); /* print num as a hexidecimal e.g. "0x12ab" */ /* string-to-fixed-point parser (inverse of FR_printNumF) */ s32 FR_numstr(const char *s, u16 radix); #endif /* FR_NO_PRINT */ /*=============================================== * Square root and hypot * * Both take fixed-radix inputs and return a result at the same radix. * Algorithm: digit-by-digit isqrt on a 64-bit accumulator (no division, * at most 32 iterations). Rounds to nearest. * * Domain error sentinel: input < 0 (sqrt) returns FR_DOMAIN_ERROR. Caller * can check `result == FR_DOMAIN_ERROR` to detect domain errors. */ s32 FR_sqrt(s32 input, u16 radix); #ifndef FR_LEAN s32 FR_hypot(s32 x, s32 y, u16 radix); #endif /* Fast approximate magnitude — shift-only, no multiply, no 64-bit. * Based on piecewise-linear approximation of sqrt(x*x + y*y). * See US Patent 6,567,777 B1 (Chatterjee, expired). * * FR_hypot_fast8(x, y) 8-segment, ~0.10% peak error * * Inputs are raw signed integers (or fixed-point at any radix — the * result is at the same radix as the inputs, just like FR_hypot). * No radix parameter needed because the algorithm is scale-invariant. */ s32 FR_hypot_fast8(s32 x, s32 y); /*=============================================== * Wave generators and ADSR envelope * * Define FR_NO_WAVES before including this header to exclude all * waveform generators (square, pulse, triangle, saw, noise) and the * ADSR envelope from compilation. This saves ~400 B of ROM on targets * that only need math/trig and don't do audio synthesis. * * #define FR_NO_WAVES * #include "FR_math.h" */ #ifndef FR_NO_WAVES /*=============================================== * Wave generators — synth-style fixed-shape waveforms. * * All take a u16 BAM phase in [0, 65535] (a full cycle) and return s16 * in s0.15 in [-32767, +32767]. Use FR_HZ2BAM_INC below to compute a * phase increment for a target frequency. * * fr_wave_sqr(phase) 50% square * fr_wave_pwm(phase, duty) variable-duty pulse * fr_wave_tri(phase) symmetric triangle * fr_wave_saw(phase) rising sawtooth * fr_wave_tri_morph(phase, brk) variable-symmetry triangle (morphs to saw) * fr_wave_noise(state*) LFSR pseudorandom noise * * fr_wave_tri_morph returns [0, 32767] (unipolar) — caller can re-bias * if a bipolar form is desired. The other waves are bipolar [-32767, +32767]. */ s16 fr_wave_sqr(u16 phase); s16 fr_wave_pwm(u16 phase, u16 duty); s16 fr_wave_tri(u16 phase); s16 fr_wave_saw(u16 phase); s16 fr_wave_tri_morph(u16 phase, u16 break_point); s16 fr_wave_noise(u32 *state); /* FR_HZ2BAM_INC(hz, sample_rate) * Compute the per-sample BAM phase increment for a target frequency in Hz * given a sample rate in Hz. Result is a u16 to add to the running phase * each sample (the running phase wraps mod 2^16 naturally because it's u16). * * u16 phase = 0; * u16 inc = FR_HZ2BAM_INC(440, 48000); * for (...) { sample = fr_sin_bam(phase); phase += inc; } * * Range: hz must be < sample_rate / 2 (Nyquist) for a meaningful tone; * higher hz aliases. The macro does not enforce this. * * Side-effect note: hz and sample_rate are evaluated once each. */ #define FR_HZ2BAM_INC(hz, sample_rate) ((u16)(((u32)(hz) * 65536UL) / (u32)(sample_rate))) /*=============================================== * ADSR envelope generator * * Linear-segment Attack-Decay-Sustain-Release envelope. Caller-allocated * struct, no malloc, no global state. Internal level is held in s1.30 so * very long envelopes (e.g. 48000-sample attack at 48 kHz) still get a * non-zero per-sample increment. Output is s0.15 in [0, 32767]. * * Lifecycle: * fr_adsr_t env; * fr_adsr_init(&env, atk_samples, dec_samples, sustain_s015, rel_samples); * fr_adsr_trigger(&env); // note-on * for (...) sample = fr_adsr_step(&env); // per-audio-sample * fr_adsr_release(&env); // note-off * for (...) sample = fr_adsr_step(&env); // until env.state == FR_ADSR_IDLE */ #define FR_ADSR_IDLE (0) #define FR_ADSR_ATTACK (1) #define FR_ADSR_DECAY (2) #define FR_ADSR_SUSTAIN (3) #define FR_ADSR_RELEASE (4) typedef struct fr_adsr_s { u8 state; /* FR_ADSR_* */ s32 level; /* current envelope value, s1.30 */ s32 sustain; /* sustain target, s1.30 */ s32 attack_inc; /* per-sample increment during attack */ s32 decay_dec; /* per-sample decrement during decay */ s32 release_dec; /* per-sample decrement during release */ } fr_adsr_t; void fr_adsr_init(fr_adsr_t *env, u32 attack_samples, u32 decay_samples, s16 sustain_level_s015, u32 release_samples); void fr_adsr_trigger(fr_adsr_t *env); void fr_adsr_release(fr_adsr_t *env); s16 fr_adsr_step(fr_adsr_t *env); #endif /* FR_NO_WAVES */ #ifdef __cplusplus } // extern "C" #endif #endif /* __FR_Math_h__ */