188 lines
6.2 KiB
Python
188 lines
6.2 KiB
Python
'''
|
|
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)
|