def calcConsts(cls, fam, famOnly=False): consts = [ fam.pi ] if not famOnly: consts.append(6 * FXnum(0.5, fam).asin()) consts.append(4 * ( 4 * (1 / FXnum(5, fam)).atan() - (1 / FXnum(239, fam)).atan() )) return consts
def testIntRange(self): """Check detection of overflow of integer part.""" for top in [-3, -2, 0, 2, 4, 6]: fam = FXfamily(16, top) a = FXnum(1.0 / 16.01, fam) zero = FXnum(0, fam) limit = 1 << (top + 4 - 1) # Unlikely to be valid FXnum(,fam) cnt, x, y = 0, zero, zero while cnt < (limit + 5): cnt += 1 try: x += a except FXoverflowError: if cnt <= limit: self.fail() else: if cnt > limit: self.fail() try: y -= a except FXoverflowError: if cnt <= limit: self.fail() else: if cnt > limit: self.fail()
def testFamilyProtection(self): """Check that arithmetic operators do not transmute resolution families""" famlist = [FXfamily(res) for res in [8, 16, 40, 90]] for fam0 in famlist: for fam1 in famlist: x = FXnum(2, fam0) y = FXnum(3, fam1) try: a = x + y except FXfamilyError: self.assertFalse(fam0 is fam1) else: self.assertTrue(fam0 is fam1) try: a = x - y except FXfamilyError: self.assertFalse(fam0 is fam1) else: self.assertTrue(fam0 is fam1) try: a = x * y except FXfamilyError: self.assertFalse(fam0 is fam1) else: self.assertTrue(fam0 is fam1) try: a = x / y except FXfamilyError: self.assertFalse(fam0 is fam1) else: self.assertTrue(fam0 is fam1)
def testMultiplication(self): """Multiplication operators should promote & commute""" scale = 0.25 scale2 = scale * scale for x in range(-16, 32): fpx = FXnum(x * scale) for y in range(-32, 16): fpy = FXnum(y * scale) fpa = FXnum((x * y) * scale2) # compute various forms of a = (x * y): self.assertEqual(fpa, fpx * fpy) self.assertEqual(fpa, fpy * fpx) self.assertEqual((x * y) * scale2, float(fpx * fpy)) tmp = fpx tmp *= fpy self.assertEqual(fpa, tmp) tmp = float(x * scale) * fpy self.assertEqual(fpa, tmp) tmp = fpx * float(y * scale) self.assertEqual(fpa, tmp)
def testDivision(self): """Division operators should promote & inverse-commute""" fam62 = FXfamily(62) scale = 0.125 scale2 = scale * scale for a in range(-32, 32): if a == 0: continue fpa = FXnum(a * scale, fam62) for y in range(-16, 16): if y == 0: continue fpy = FXnum(y * scale, fam62) fpx = FXnum((y * a) * scale2, fam62) # compute various forms of a = (x / y): self.assertAlmostEqual(fpa, fpx / fpy) self.assertAlmostEqual(1 / fpa, fpy / fpx) self.assertAlmostEqual((a * scale), float(fpx / fpy)) tmp = fpx tmp /= fpy self.assertAlmostEqual(fpa, tmp) tmp = float(a * y * scale2) / fpy self.assertAlmostEqual(fpa, tmp) tmp = fpx / float(y * scale) self.assertAlmostEqual(fpa, tmp)
def testBoolConditions(self): """Values used in boolean expressions should behave as true/false""" if FXnum(0): self.fail() if FXnum(1): pass else: self.fail()
def testRawBuild(self): """Check equivalence of FXnum._rawbuild() and FXnum().""" fam = FXfamily(10, 5) x = FXnum(val=9.7531, family=fam) xraw = FXnum._rawbuild(fam, x.scaledval) self.assertEqual(x, xraw) self.assertIsInstance(x, FXnum) self.assertIsInstance(xraw, FXnum) self.assertIsNot(x, xraw)
def testRepresentation(self): """Check repr() functionality""" for i in range(1, 30): v = 1.7**i for x in [v, -v, 1.0 / v, -1.0 / v]: fp0 = FXnum(x) # ensure that value extracted via repr() is in same FXfamily: fpb = FXnum(eval(repr(fp0)), family=fp0.family) self.assertEqual(fp0, fpb)
def testPrinting(self): """Check conversion to string""" for i in range(1, 10): v = 2**i for x in [v, -v, 1.0 / v, -1.0 / v]: plainstr = "%.8g" % x fpx = FXnum(x) self.assertEqual('{:.8g}'.format(x), str(fpx)) self.assertEqual(str(fpx), fpx.toDecimalString())
def testExpLog(self): """exp and log methods should be inverses & agree with math.*""" fam62 = FXfamily(62) scale = 0.27 for i in range(-32, 32): x = i * scale exp = FXnum(x, fam62).exp() logexp = exp.log() self.assertAlmostEqual(x, float(logexp))
def testPrinting(self): """Check conversion to string""" for i in range(1, 10): v = 2 ** i for x in [v, -v, 1.0/v, -1.0/v]: plainstr = "%.8g" % x fpx = FXnum(x) self.assertEqual('{:.8g}'.format(x), str(fpx)) self.assertEqual(str(fpx), fpx.toDecimalString())
def testArctan(self): """atan method agree with math.sin/cos""" fam62 = FXfamily(62) scale = 0.277 self.assertEqual(FXnum(0, fam62), 0) for i in range(-32, 32): tan = i * scale ang_true = math.atan(tan) ang = FXnum(tan, fam62).atan() self.assertAlmostEqual(ang_true, ang) self.assertAlmostEqual(float(ang.tan()), tan)
def testNegating(self): """Check prefix operators""" fam17 = FXfamily(17) for i in range(-32, 32): x = i * 0.819 fx = FXnum(x, fam17) zero = FXnum(0, fam17) try: self.assertEqual(zero, fx + (-fx)) self.assertEqual(zero, -fx + fx) self.assertEqual((-1 * fx), -fx) self.assertEqual(zero, (-1 * fx) + (-fx) + 2 * (+fx)) except FXexception: self.fail()
def next_sample(self, fcw: int) -> NCOType: lut_index = (self.phase_acc >> (self.N - self.M)) & int( '1' * self.M, 2) # take MSB M bits of phase_acc samples = [] for lut in [ self.sine_lut_fixed, self.square_lut_fixed, self.triangle_lut_fixed, self.sawtooth_lut_fixed ]: if self.interpolate is False: samples.append(lut[lut_index]) else: samp1 = lut[lut_index] samp2 = lut[(lut_index + 1) % self.lut_entries] residual = self.phase_acc & int( '1' * (self.N - self.M), 2) # take LSB (N-M) bits of phase_acc residual = FXnum( residual / (2**(self.N - self.M)), family=self.output_type) # Cast residual as fixed point diff = samp2 - samp1 samples.append(samp1 + residual * diff) self.phase_acc = self.phase_acc + fcw self.phase_acc = self.phase_acc % 2**self.N # overflow on N bits return samples
def testPow(self): """Check raising FXnums to powers.""" fam62 = FXfamily(62) scale = 0.205 scale2 = 0.382 for i in range(1, 32): x = i * scale pwr = FXnum(0, fam62)**x self.assertEqual(FXnum(1, fam62), pwr) for j in range(-16, 16): y = j * scale2 pwr_true = math.pow(x, y) pwr = FXnum(x, fam62)**y self.assertAlmostEqual(pwr_true, pwr)
def testArcSinCos(self): """asin/acos methods should be inverses of sin/cos""" fam62 = FXfamily(62) trig = FXnum(0, fam62) self.assertEqual(trig, trig.asin()) self.assertEqual(trig, FXnum(1,fam62).acos()) steps = 20 for i in range(0, steps + 1): for s in [-1.0, 1.0]: trig = FXnum((i * s) / steps, fam62) isn = trig.asin() self.assertTrue(abs(isn) <= fam62.pi / 2) self.assertAlmostEqual(float(trig), float(isn.sin())) ics = trig.acos() self.assertTrue(0 <= ics and ics <= fam62.pi) self.assertAlmostEqual(float(trig), float(ics.cos()))
def testSqrt(self): """sqrt method should find square-roots""" fam62 = FXfamily(62) scale = 0.94 for i in range(-40, 40): x = i * scale fx = FXnum(x, fam62) try: rt = fx.sqrt() except FXdomainError: self.assertFalse(x >= 0) else: rt2 = float(rt * rt) self.assertAlmostEqual(x, rt2) if i == 0: self.assertEqual(FXnum(0, fam62), rt)
def next_sample(self) -> int: nco_out = self.nco.next_sample_f(self.note_freq) summer_out = self.summer.next_sample(nco_out) if self.playing_note: return summer_out else: return FXnum(0, output_type)
def testDecimalRound(self): third = FXnum(1, FXfamily(200)) / 3 x23 = 2 * third self.assertEqual(third.toDecimalString(precision=5), '0.33333') self.assertEqual(third.toDecimalString(precision=8, round10=True), '0.33333333') self.assertEqual(x23.toDecimalString(precision=4), '0.6666') self.assertEqual(x23.toDecimalString(precision=7, round10=True), '0.6666667') self.assertEqual(x23.toDecimalString()[:42], '0.' + '6' * 40) self.assertEqual(x23.toDecimalString(precision=50), '0.' + '6' * 50) self.assertEqual(x23.toDecimalString(precision=50, round10=True), '0.' + '6' * 49 + '7')
def testBinarySeries(self): """Check binary printing of enumerated signed integers.""" for res in range(1, 5): fam = FXfamily(res) for scaled in range(1 << (res + 4)): pos = FXnum(family=fam, scaled_value=scaled) for sign in ((1, -1) if scaled > 0 else (1, )): printed = (sign * pos).toBinaryString() if sign > 0: x = scaled else: if scaled < (1 << res): shift = res + 1 else: shift = math.ceil(math.log2(scaled)) + 1 x = (1 << shift) - scaled binstr = bin(x)[2:] pad = res + 1 - len(binstr) if pad > 0: binstr = '0' * pad + binstr binstr = binstr[:-res] + '.' + binstr[-res:] self.assertEqual(binstr, printed, 'Binary printing failed for {}, res={}' \ .format(float(sign * pos), res))
def testPrinting(self): """Check conversion to string""" for i in range(1, 10): v = 2**i for x in [v, -v, 1.0 / v, -1.0 / v]: fpa = "%.8g" % x fpx = str(FXnum(x)) self.assertEqual(fpa, fpx)
def testSinCos(self): """sin/cos methods agree with math.sin/cos""" fam62 = FXfamily(62) scale = 0.342 fang = FXnum(0, fam62) self.assertEqual(fang, fang.sin()) self.assertEqual(FXnum(1, fam62), fang.cos()) for i in range(-32, 32): x = i * scale sin_true = math.sin(x) cos_true = math.cos(x) fang = FXnum(x, fam62) (sin, cos) = fang.sincos() self.assertAlmostEqual(sin_true, sin) self.assertAlmostEqual(cos_true, cos) self.assertEqual(sin, fang.sin()) self.assertEqual(cos, fang.cos())
def testNegatives(self): fam = FXfamily(3) x2 = FXnum(-0.25, fam) self.assertEqual(x2.toBinaryString(), '1.110') self.assertEqual(x2.toBinaryString(twosComp=False), '-0.010') x3 = FXnum(-0.625, fam) self.assertEqual(x3.toBinaryString(logBase=3), '7.3') self.assertEqual(x3.toBinaryString(3, twosComp=False), '-0.5') x4 = FXnum(-0.875, fam) self.assertEqual(x4.toBinaryString(4), 'f.2') self.assertEqual(x4.toBinaryString(4, twosComp=False), '-0.e')
def testDecimalRound(self): third = FXnum(1, FXfamily(200)) / 3 x23 = 2 * third self.assertEqual(third.toDecimalString(precision=5), '0.33333') self.assertEqual(third.toDecimalString(precision=8, round10=True), '0.33333333') self.assertEqual(x23.toDecimalString(precision=4), '0.6666') self.assertEqual(x23.toDecimalString(precision=7, round10=True), '0.6666667') self.assertEqual(x23.toDecimalString()[:42], '0.' + '6'*40) self.assertEqual(x23.toDecimalString(precision=50), '0.' + '6'*50) self.assertEqual(x23.toDecimalString(precision=50, round10=True), '0.' + '6' * 49 + '7')
def testFamilyConversion(self): """Check that conversion between families preserves values""" famlist = [FXfamily(b) for b in [32, 40, 48, 80, 120]] for i in range(1, 10): xpos = (float)(1 << (2 * i)) + 1.0 / (1 << i) for x in [xpos, -xpos]: for fam0 in famlist: fp0 = FXnum(x, fam0) for fam1 in famlist: fp1 = FXnum(fp0, fam1) try: f = (fp0 == fp1) except FXfamilyError: self.assertFalse(fam0 is fam1) else: self.assertTrue(fam0 is fam1) self.assertAlmostEqual(x, float(fp0)) self.assertAlmostEqual(x, float(fp1))
def testExp(self): """Exponent method should agree with math.exp""" fam62 = FXfamily(62) scale = 0.23 for i in range(-32, 32): x = i * scale exp_true = math.exp(x) exp = FXnum(x, fam62).exp() self.assertAlmostEqual(exp_true, exp)
def testNumCreateRounding(self): """Check rounding behaviour on FXnum creation""" fam = FXfamily(3) self.assertEqual(FXnum(0.375, fam).scaledval, 3) self.assertEqual(FXnum(0.437, fam).scaledval, 3) self.assertEqual(FXnum(0.438, fam).scaledval, 4) self.assertEqual(FXnum(0.46, fam).scaledval, 4) fam = FXfamily(4) self.assertEqual(FXnum(-0.75, fam).scaledval, -12) self.assertEqual(FXnum(-0.78124, fam).scaledval, -12) self.assertEqual(FXnum(-0.78126, fam).scaledval, -13) self.assertEqual(FXnum(-0.8125, fam).scaledval, -13)
def testLog(self): """Logarithm method agree with math.log""" fam62 = FXfamily(62) base = 1.5 for i in range(1, 32): for j in range(0, 2): if j == 0: x = 1.0 / (base**i) else: x = base**i log_true = math.log(x) log = FXnum(x, fam62).log() self.assertAlmostEqual(log_true, log)
def __init__(self, fsamp: float, interpolate: bool) -> None: self.fsamp = fsamp self.interpolate = interpolate self.output_type = output_type self.phase_acc = 0 # type: int self.N = 24 self.M = 8 self.lut_entries = 2**self.M self.sine_lut_float = [ np.sin(i * 2 * np.pi / self.lut_entries) for i in range(self.lut_entries) ] self.sine_lut_fixed = [ FXnum(x, family=self.output_type) for x in self.sine_lut_float ] self.square_lut_fixed = [FXnum(1, family=self.output_type) for x in range(int(self.lut_entries/2))] + \ [FXnum(-1, family=self.output_type) for x in range(int(self.lut_entries/2))] self.triangle_lut_float = [ np.max(1 - np.abs(x)) for x in np.linspace(-1, 1, self.lut_entries) ] self.triangle_lut_float = [x * 2 - 1 for x in self.triangle_lut_float ] # scale to range from -1 to 1 self.triangle_lut_fixed = [ FXnum(x, family=self.output_type) for x in self.triangle_lut_float ] self.sawtooth_lut_float = [ x - np.floor(x) for x in np.linspace(0, 1 - 1e-16, self.lut_entries) ] self.sawtooth_lut_float = [x * 2 - 1 for x in self.sawtooth_lut_float ] # scaling again self.sawtooth_lut_fixed = [ FXnum(x, family=self.output_type) for x in self.sawtooth_lut_float ]
def testAddition(self): """Addition operators should promote & commute""" scale = 0.125 for x in range(-16, 16): fpx = FXnum(x * scale) for y in range(-32, 32): fpy = FXnum(y * scale) fpa = FXnum((x + y) * scale) # compute various forms of a = (x + y): self.assertEqual(fpa, fpx + fpy) self.assertEqual(fpa, fpy + fpx) self.assertEqual((x + y) * scale, float(fpx + fpy)) tmp = fpx tmp += fpy self.assertEqual(fpa, tmp) tmp = float(x * scale) + fpy self.assertEqual(fpa, tmp) tmp = fpx + float(y * scale) self.assertEqual(fpa, tmp)
def testSubtraction(self): """Subtraction operators should promote & anti-commute""" scale = 0.0625 for x in range(-32, 16): fpx = FXnum(x * scale) for y in range(-16, 32): fpy = FXnum(y * scale) fpa = FXnum((x - y) * scale) # compute various forms of a = (x - y): self.assertEqual(fpa, fpx - fpy) self.assertEqual(-fpa, fpy - fpx) self.assertEqual((x - y) * scale, float(fpx - fpy)) tmp = fpx tmp -= fpy self.assertEqual(fpa, tmp) tmp = float(x * scale) - fpy self.assertEqual(fpa, tmp) tmp = fpx - float(y * scale) self.assertEqual(fpa, tmp)
def testBitShifts(self): """Check effects of left & right shift operators.""" fam = FXfamily(32) self.assertEqual(FXnum(1, fam) << 2, 4) self.assertEqual(FXnum(3, fam) << 4, 48) self.assertEqual(FXnum(-7, fam) << 8, -7 * 256) self.assertEqual(FXnum(1, fam) >> 1, 0.5) self.assertEqual(FXnum(12, fam) >> 2, 3) self.assertEqual(FXnum(-71 * 1024, fam) >> 12, -17.75)
def testSinCos(self): """sin/cos methods agree with math.sin/cos""" fam62 = FXfamily(62) scale = 0.342 fang = FXnum(0, fam62) self.assertEqual(fang, fang.sin()) self.assertEqual(FXnum(1,fam62), fang.cos()) for i in range(-32, 32): x = i * scale sin_true = math.sin(x) cos_true = math.cos(x) fang = FXnum(x, fam62) (sin, cos) = fang.sincos() self.assertAlmostEqual(sin_true, sin) self.assertAlmostEqual(cos_true, cos) self.assertEqual(sin, fang.sin()) self.assertEqual(cos, fang.cos())
def testPowSpecials(self): """Check raising of special values to powers.""" fam30 = FXfamily(30) for i in range(1, 30): # Check x^0 == 1 self.assertEqual(FXnum(i * 0.2, fam30)**0, fam30.unity) # Check 0^n == 0 self.assertEqual(fam30.zero**i, fam30.zero) # Check 0^x == 0 self.assertEqual(fam30.zero**(i * 0.27), fam30.zero) # Check 0^-x with self.assertRaises(FXdomainError): fam30.zero**(1 - i) with self.assertRaises(FXdomainError): fam30.zero**-(i * 0.23)
def testArcSinCos(self): """asin/acos methods should be inverses of sin/cos""" fam62 = FXfamily(62) trig = FXnum(0, fam62) self.assertEqual(trig, trig.asin()) self.assertEqual(trig, FXnum(1, fam62).acos()) steps = 20 for i in range(0, steps + 1): for s in [-1.0, 1.0]: trig = FXnum((i * s) / steps, fam62) isn = trig.asin() self.assertTrue(abs(isn) <= fam62.pi / 2) self.assertAlmostEqual(float(trig), float(isn.sin())) self.assertAlmostEqual(float(isn), math.asin(float(trig))) ics = trig.acos() self.assertTrue(0 <= ics and ics <= fam62.pi) self.assertAlmostEqual(float(trig), float(ics.cos())) self.assertAlmostEqual(float(ics), math.acos(float(trig)))