''' Calculate the optimum bin sizes to fit the selcal tone assignments ''' from __future__ import print_function import bidict import math class Tones(object): """ The selcal tone definitions """ RED = bidict.bidict( # Designation: Frequency (Hz) Alpha=312.6, Bravo=346.7, Charlie=384.6, Delta=426.6, Echo=473.2, Foxtrot=524.8, Golf=582.1, Hotel=645.7, Juliet=716.1, Kilo=794.3, Lima=881.0, Mike=977.2, Papa=1083.9, Quebec=1202.3, Romeo=1333.5, Sierra=1479.1,) def freq(self, desig): """ :return: The frequency assigned to the given designator """ return Tones.RED[desig] def tone(self, freq): """ :return: The designator for the given frequency """ try: return Tones.RED[:freq] except Exception: pass class SelcalParams: """ Contains the specifications and parameters for the set of 16 selcal tones. The specified frequencies, tolerances, and associated DFT bins are attributes. """ def __init__(self, samplerate): self.samplerate = samplerate * 1.0 self.selcal_tones = [312.6, 346.7, 384.6, 426.6, 473.2, 524.8, 582.1, 645.7, 716.1, 794.3, 881.0, 977.2, 1083.9, 1202.3, 1333.5, 1479.1, ] self.log_tone_step = 0.045 self.log_tone_tolerance = 0.0015 self.tone_lower = {} self.tone_upper = {} self.tone_step = {} self.tone_window = {} self.bin_center = {} self.bin_error = {} for tone in range(len(self.selcal_tones)): self.tone_lower[tone] = \ self.selcal_tones[tone] * (1 - self.log_tone_tolerance) self.tone_upper[tone] = \ self.selcal_tones[tone] * (1 + self.log_tone_tolerance) self.tone_window[tone] = \ self.tone_upper[tone] - self.tone_lower[tone] for tone in range(12, 28): index = tone - 12 lower = math.pow(10, (2.0 + ((tone - 1) * self.log_tone_step))) upper = math.pow(10, (2.0 + (tone * self.log_tone_step))) self.tone_step[index] = (upper - lower) def calc_bin_error(self, binsize): """ For a given sample block size, calculate the freqency error between the derived DFT frequency bins and the selcal tones. Return an arbitrary score for the net frequency error across all of the selcal tones. Currently, this is set to a log10 value, so more negative scores are better. """ self.samplesize = binsize self.binsize = (self.samplerate / self.samplesize) self.bintime = (self.samplesize / self.samplerate) avg_err = 0.0 for tone in range(len(self.selcal_tones)): self.bin_center[tone] = \ ((1.0 * int(self.selcal_tones[tone] / self.binsize)) + 0.5) \ * self.binsize bin_err = abs(self.selcal_tones[tone] - self.bin_center[tone]) self.bin_error[tone] = bin_err if bin_err > 0: avg_err = avg_err + math.log10(bin_err) return(avg_err) def print_bin_error(self, samplerate, samplesize): """ Given a sample rate and size, print out the frequency error for each DFT bin versus the selcal tone frequency. """ binsize = (samplerate / samplesize) print("=== Rate: ", samplerate, " size : ", samplesize, " time: ", samplesize * 1.0 / samplerate, " ===") for tone in range(len(self.selcal_tones)): self.bin_center[tone] = \ ((1.0 * int(self.selcal_tones[tone] / binsize)) + 0.5) \ * binsize bin_err = self.selcal_tones[tone] - self.bin_center[tone] print("Tone: ", tone, " freq: ", self.selcal_tones[tone], " bin: ", self.bin_center[tone], " err: ", bin_err, " tol: ", self.selcal_tones[tone] * self.log_tone_tolerance) print() def search_err(self, upper_bound): """ Search sample sizes starting from a given upper bound to a set lower bound, recursively. Return when the upper bound equals the lower bound. """ lower_bound = int(self.samplerate / 40) if upper_bound > lower_bound: min_err = 10000.0 min_size = upper_bound for size in range(lower_bound, upper_bound): err = params.calc_bin_error(size) if err < min_err: min_err = err min_size = size print("rate: ", rate, " size ", min_size, " time ", (min_size * 1.0 / rate), " error ", min_err) self.search_err(min_size) if __name__ == '__main__': _samplerates = [44100, 32000, 22050, 16000, 11025, 8000, 4000] for rate in _samplerates: params = SelcalParams(rate) upper_bound = int(rate / 9) params.search_err(upper_bound) print() # rate: 44100 size 4580 time 0.103854875283 error -3.60366543025 params.print_bin_error(44100, 4580) # rate: 32000 size 3553 time 0.11103125 error -3.36174781256 params.print_bin_error(32000, 3553) # rate: 22050 size 2290 time 0.103854875283 error -3.60366543025 params.print_bin_error(22050, 2290) # rate: 16000 size 1662 time 0.103875 error -2.49558719312 params.print_bin_error(16000, 1662) # rate: 11025 size 1145 time 0.103854875283 error -3.60366543025 params.print_bin_error(11025, 1145) # rate: 8000 size 831 time 0.103875 error -2.49558719312 params.print_bin_error(8000, 831) # rate: 4000 size 361 time 0.09025 error -1.53481794027 params.print_bin_error(4000, 361)