2018-04-23 07:29:21 +00:00
|
|
|
|
#!/usr/bin/env python3
|
2018-07-31 09:23:24 +00:00
|
|
|
|
# -*- coding: utf-8 -*-
|
2020-03-01 14:06:01 +00:00
|
|
|
|
import os
|
|
|
|
|
import platform
|
|
|
|
|
import shelve
|
2020-04-06 19:38:04 +00:00
|
|
|
|
import sys
|
2020-03-01 14:06:01 +00:00
|
|
|
|
import appdirs
|
2018-04-23 07:29:21 +00:00
|
|
|
|
import numpy as np
|
2020-03-01 14:06:01 +00:00
|
|
|
|
|
2018-05-02 14:29:53 +00:00
|
|
|
|
from .wrappers import Window as wWindow
|
2020-05-28 19:26:30 +00:00
|
|
|
|
from collections import namedtuple
|
2020-08-03 18:17:52 +00:00
|
|
|
|
from dataclasses import dataclass
|
|
|
|
|
from dataclasses_json import dataclass_json
|
2020-03-01 14:06:01 +00:00
|
|
|
|
|
2018-04-23 07:29:21 +00:00
|
|
|
|
"""
|
|
|
|
|
Common definitions used throughout the code.
|
|
|
|
|
"""
|
|
|
|
|
|
2020-03-01 14:06:01 +00:00
|
|
|
|
__all__ = [
|
|
|
|
|
'P_REF', 'FreqWeighting', 'TimeWeighting', 'getTime', 'getFreq',
|
2020-05-28 19:26:30 +00:00
|
|
|
|
'lasp_shelve', 'this_lasp_shelve', 'W_REF', 'U_REF', 'I_REF', 'dBFS_REF',
|
2020-03-01 14:06:01 +00:00
|
|
|
|
]
|
2018-04-23 07:29:21 +00:00
|
|
|
|
|
2020-05-28 19:26:30 +00:00
|
|
|
|
# Reference sound pressure level
|
|
|
|
|
P_REF = 2e-5 # 20 micropascal
|
|
|
|
|
|
|
|
|
|
W_REF = 1e-12 # 1 picoWatt
|
|
|
|
|
I_REF = 1e-12 # 1 picoWatt/ m^2
|
|
|
|
|
|
|
|
|
|
# Reference velocity for sound velocity level
|
|
|
|
|
U_REF = 5e-8 # 50 nano meter / s
|
|
|
|
|
|
|
|
|
|
# Wiki: The unit dB FS or dBFS is defined in AES Standard AES17-1998,[13]
|
|
|
|
|
# IEC 61606,[14] and ITU-T P.38x,[15][16] such that the RMS value of a
|
|
|
|
|
# full-scale sine wave is designated 0 dB FS
|
|
|
|
|
|
|
|
|
|
# The LASP code converts integer samples to floats, where the mapping is such
|
|
|
|
|
# that the maximum value (POSITIVE) that can be represented by the integer data
|
|
|
|
|
# is mapped to the value of 1.0. Following this convention, a sine wave with
|
|
|
|
|
# amplitude of 1.0 is 0 dB FS, and subsequently, its rms value is 0.5*sqrt(2),
|
|
|
|
|
# hence this is the reference level as specified below.
|
|
|
|
|
dBFS_REF = 0.5*2**0.5 # Which level would be -3.01 dBFS
|
|
|
|
|
|
2020-08-03 18:17:52 +00:00
|
|
|
|
@dataclass_json
|
|
|
|
|
@dataclass
|
|
|
|
|
class Qty:
|
|
|
|
|
name: str
|
|
|
|
|
unit_name: str
|
|
|
|
|
unit_symb: str
|
|
|
|
|
level_unit: str
|
|
|
|
|
level_ref_name: str
|
|
|
|
|
level_ref_value: str
|
2020-05-28 19:26:30 +00:00
|
|
|
|
|
2020-08-22 17:53:39 +00:00
|
|
|
|
|
2020-05-28 19:26:30 +00:00
|
|
|
|
class SIQtys:
|
2020-08-03 18:17:52 +00:00
|
|
|
|
N = Qty(name='Number',
|
|
|
|
|
unit_name='No unit / full scale',
|
|
|
|
|
unit_symb=('-'),
|
|
|
|
|
level_unit=('dBFS',),
|
|
|
|
|
level_ref_name=('Full scale sine wave',),
|
|
|
|
|
level_ref_value=(dBFS_REF,)
|
|
|
|
|
)
|
2020-05-28 19:26:30 +00:00
|
|
|
|
AP = Qty(name='Acoustic Pressure',
|
|
|
|
|
unit_name='Pascal',
|
|
|
|
|
unit_symb=('Pa', 'muPa'),
|
|
|
|
|
level_unit=('dB SPL','dBPa'),
|
|
|
|
|
level_ref_name=('2 micropascal', '1 pascal',),
|
|
|
|
|
level_ref_value=(P_REF, 1)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
V = Qty(name='Voltage',
|
2020-08-03 18:17:52 +00:00
|
|
|
|
unit_name='Volt',
|
2020-05-28 19:26:30 +00:00
|
|
|
|
unit_symb='V',
|
|
|
|
|
level_unit=('dBV',), # dBV
|
|
|
|
|
level_ref_name=('1V',),
|
|
|
|
|
level_ref_value=(1.0,),
|
|
|
|
|
)
|
2020-08-05 07:56:58 +00:00
|
|
|
|
types = (N, AP, V)
|
2020-08-03 18:17:52 +00:00
|
|
|
|
default = N
|
2020-05-28 19:26:30 +00:00
|
|
|
|
default_index = 0
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def fillComboBox(cb):
|
|
|
|
|
"""
|
|
|
|
|
Fill FreqWeightings to a combobox
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
cb: QComboBox to fill
|
|
|
|
|
"""
|
|
|
|
|
cb.clear()
|
|
|
|
|
for ty in SIQtys.types:
|
2020-08-03 18:17:52 +00:00
|
|
|
|
cb.addItem(f'{ty.unit_name}')
|
2020-05-28 19:26:30 +00:00
|
|
|
|
cb.setCurrentIndex(SIQtys.default_index)
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def getCurrent(cb):
|
|
|
|
|
return SIQtys.types[cb.currentIndex()]
|
|
|
|
|
|
2020-08-22 17:53:39 +00:00
|
|
|
|
@dataclass
|
|
|
|
|
class CalSetting:
|
|
|
|
|
name: str
|
2020-09-18 06:55:24 +00:00
|
|
|
|
cal_value_dB: float
|
|
|
|
|
cal_value_linear: float
|
2020-08-22 17:53:39 +00:00
|
|
|
|
qty: Qty
|
|
|
|
|
|
|
|
|
|
class CalibrationSettings:
|
2020-09-18 06:55:24 +00:00
|
|
|
|
one = CalSetting('94 dB SPL', 94.0 , 1.0, SIQtys.AP)
|
|
|
|
|
two = CalSetting('114 dB SPL', 114.0 , 10.0, SIQtys.AP)
|
2020-08-22 17:53:39 +00:00
|
|
|
|
|
|
|
|
|
types = (one, two)
|
|
|
|
|
default = one
|
|
|
|
|
default_index = 0
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def fillComboBox(cb):
|
|
|
|
|
"""
|
|
|
|
|
Fill FreqWeightings to a combobox
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
cb: QComboBox to fill
|
|
|
|
|
"""
|
|
|
|
|
cb.clear()
|
|
|
|
|
for ty in CalibrationSettings.types:
|
2020-09-18 06:55:24 +00:00
|
|
|
|
cb.addItem(f'{ty.cal_value_dB}')
|
2020-08-22 17:53:39 +00:00
|
|
|
|
cb.setCurrentIndex(CalibrationSettings.default_index)
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def getCurrent(cb):
|
2020-09-18 06:55:24 +00:00
|
|
|
|
if cb.currentIndex() < len(CalibrationSettings.types):
|
|
|
|
|
return CalibrationSettings.types[cb.currentIndex()]
|
|
|
|
|
else:
|
|
|
|
|
return None
|
2020-05-28 19:26:30 +00:00
|
|
|
|
|
2019-12-08 13:19:10 +00:00
|
|
|
|
lasp_appdir = appdirs.user_data_dir('Lasp', 'ASCEE')
|
|
|
|
|
|
|
|
|
|
if not os.path.exists(lasp_appdir):
|
|
|
|
|
try:
|
2020-04-06 19:38:04 +00:00
|
|
|
|
os.makedirs(lasp_appdir, exist_ok=True)
|
2019-12-08 13:19:10 +00:00
|
|
|
|
except:
|
|
|
|
|
print('Fatal error: could not create application directory')
|
2020-04-06 19:38:04 +00:00
|
|
|
|
sys.exit(1)
|
2019-12-08 13:19:10 +00:00
|
|
|
|
|
2020-05-28 19:26:30 +00:00
|
|
|
|
|
2020-08-22 17:53:39 +00:00
|
|
|
|
|
2020-03-05 08:43:33 +00:00
|
|
|
|
class Shelve:
|
|
|
|
|
def load(self, key, default_value):
|
|
|
|
|
"""
|
|
|
|
|
Load data from a given key, if key is not found, returns the
|
|
|
|
|
default value if key is not found
|
|
|
|
|
"""
|
|
|
|
|
if key in self.shelve.keys():
|
|
|
|
|
return self.shelve[key]
|
|
|
|
|
else:
|
|
|
|
|
return default_value
|
2019-12-17 13:09:45 +00:00
|
|
|
|
|
2019-12-08 13:19:10 +00:00
|
|
|
|
def __enter__(self):
|
2020-03-09 12:29:14 +00:00
|
|
|
|
self.incref()
|
2020-03-05 08:43:33 +00:00
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
def store(self, key, val):
|
2020-03-09 12:29:14 +00:00
|
|
|
|
self._shelve[key] = val
|
2020-03-05 08:43:33 +00:00
|
|
|
|
|
|
|
|
|
def deleteIfPresent(self, key):
|
|
|
|
|
try:
|
2020-03-09 12:29:14 +00:00
|
|
|
|
del self._shelve[key]
|
2020-03-05 08:43:33 +00:00
|
|
|
|
except:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
def printAllKeys(self):
|
|
|
|
|
print(list(self.shelve.keys()))
|
|
|
|
|
|
2020-03-09 12:29:14 +00:00
|
|
|
|
def incref(self):
|
|
|
|
|
if self.shelve is None:
|
|
|
|
|
assert self.refcount == 0
|
|
|
|
|
self.shelve = shelve.open(self.shelve_fn())
|
|
|
|
|
self.refcount += 1
|
2019-12-08 13:19:10 +00:00
|
|
|
|
|
2020-03-09 12:29:14 +00:00
|
|
|
|
def decref(self):
|
2020-03-05 08:43:33 +00:00
|
|
|
|
self.refcount -= 1
|
|
|
|
|
if self.refcount == 0:
|
|
|
|
|
self.shelve.close()
|
|
|
|
|
self.shelve = None
|
2019-12-08 13:19:10 +00:00
|
|
|
|
|
2020-03-09 12:29:14 +00:00
|
|
|
|
def __exit__(self, type, value, traceback):
|
|
|
|
|
self.decref()
|
|
|
|
|
|
2020-05-28 19:26:30 +00:00
|
|
|
|
|
2020-03-05 08:43:33 +00:00
|
|
|
|
class lasp_shelve(Shelve):
|
2020-03-09 12:29:14 +00:00
|
|
|
|
_refcount = 0
|
2020-03-05 08:43:33 +00:00
|
|
|
|
_shelve = None
|
|
|
|
|
|
2020-03-09 12:29:14 +00:00
|
|
|
|
@property
|
|
|
|
|
def refcount(self):
|
|
|
|
|
return lasp_shelve._refcount
|
|
|
|
|
|
|
|
|
|
@refcount.setter
|
|
|
|
|
def refcount(self, val):
|
|
|
|
|
lasp_shelve._refcount = val
|
|
|
|
|
|
2020-03-05 08:43:33 +00:00
|
|
|
|
@property
|
|
|
|
|
def shelve(self):
|
2020-03-09 12:29:14 +00:00
|
|
|
|
return lasp_shelve._shelve
|
2020-03-05 08:43:33 +00:00
|
|
|
|
|
|
|
|
|
@shelve.setter
|
|
|
|
|
def shelve(self, shelve):
|
2020-03-09 12:29:14 +00:00
|
|
|
|
lasp_shelve._shelve = shelve
|
2020-03-05 08:43:33 +00:00
|
|
|
|
|
|
|
|
|
def shelve_fn(self):
|
|
|
|
|
return os.path.join(lasp_appdir, 'config.shelve')
|
2020-03-01 14:06:01 +00:00
|
|
|
|
|
2020-05-28 19:26:30 +00:00
|
|
|
|
|
2020-03-05 08:43:33 +00:00
|
|
|
|
class this_lasp_shelve(Shelve):
|
2020-03-09 12:29:14 +00:00
|
|
|
|
_refcount = 0
|
2020-03-05 08:43:33 +00:00
|
|
|
|
_shelve = None
|
2020-03-01 14:06:01 +00:00
|
|
|
|
|
2020-03-09 12:29:14 +00:00
|
|
|
|
@property
|
|
|
|
|
def refcount(self):
|
|
|
|
|
return this_lasp_shelve._refcount
|
|
|
|
|
|
|
|
|
|
@refcount.setter
|
|
|
|
|
def refcount(self, val):
|
|
|
|
|
this_lasp_shelve._refcount = val
|
|
|
|
|
|
2020-03-05 08:43:33 +00:00
|
|
|
|
@property
|
|
|
|
|
def shelve(self):
|
2020-03-09 12:29:14 +00:00
|
|
|
|
return this_lasp_shelve._shelve
|
2020-03-01 14:06:01 +00:00
|
|
|
|
|
2020-03-05 08:43:33 +00:00
|
|
|
|
@shelve.setter
|
|
|
|
|
def shelve(self, shelve):
|
2020-03-09 12:29:14 +00:00
|
|
|
|
this_lasp_shelve._shelve = shelve
|
2020-03-05 08:43:33 +00:00
|
|
|
|
|
|
|
|
|
def shelve_fn(self):
|
|
|
|
|
node = platform.node()
|
|
|
|
|
return os.path.join(lasp_appdir, f'{node}_config.shelve')
|
2020-03-01 14:06:01 +00:00
|
|
|
|
|
|
|
|
|
|
2018-05-02 14:29:53 +00:00
|
|
|
|
class Window:
|
|
|
|
|
hann = (wWindow.hann, 'Hann')
|
|
|
|
|
hamming = (wWindow.hamming, 'Hamming')
|
|
|
|
|
rectangular = (wWindow.rectangular, 'Rectangular')
|
|
|
|
|
bartlett = (wWindow.bartlett, 'Bartlett')
|
|
|
|
|
blackman = (wWindow.blackman, 'Blackman')
|
|
|
|
|
|
|
|
|
|
types = (hann, hamming, rectangular, bartlett, blackman)
|
|
|
|
|
default = 0
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def fillComboBox(cb):
|
|
|
|
|
"""
|
|
|
|
|
Fill Windows to a combobox
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
cb: QComboBox to fill
|
|
|
|
|
"""
|
|
|
|
|
cb.clear()
|
|
|
|
|
for tw in Window.types:
|
|
|
|
|
cb.addItem(tw[1], tw)
|
|
|
|
|
cb.setCurrentIndex(Window.default)
|
|
|
|
|
|
|
|
|
|
def getCurrent(cb):
|
|
|
|
|
return Window.types[cb.currentIndex()]
|
|
|
|
|
|
2020-05-28 19:26:30 +00:00
|
|
|
|
|
2018-04-23 07:29:21 +00:00
|
|
|
|
class TimeWeighting:
|
2020-01-21 20:10:38 +00:00
|
|
|
|
none = (-1, 'Raw (no time weighting)')
|
2018-05-02 14:29:53 +00:00
|
|
|
|
uufast = (1e-4, '0.1 ms')
|
2020-01-21 20:10:38 +00:00
|
|
|
|
ufast = (35e-3, 'Impulse (35 ms)')
|
2019-03-28 20:26:11 +00:00
|
|
|
|
fast = (0.125, 'Fast (0.125 s)')
|
|
|
|
|
slow = (1.0, 'Slow (1.0 s)')
|
2020-01-24 19:44:12 +00:00
|
|
|
|
tens = (10., '10 s')
|
2020-02-29 17:07:46 +00:00
|
|
|
|
infinite = (0, 'Infinite')
|
2020-08-20 08:23:24 +00:00
|
|
|
|
types_realtime = (ufast, fast, slow, tens, infinite)
|
2020-02-29 17:07:46 +00:00
|
|
|
|
types_all = (none, uufast, ufast, fast, slow, tens, infinite)
|
2020-01-21 20:10:38 +00:00
|
|
|
|
default = fast
|
2020-01-24 19:44:12 +00:00
|
|
|
|
default_index = 3
|
2020-08-20 08:23:24 +00:00
|
|
|
|
default_index_realtime = 1
|
2018-04-23 07:29:21 +00:00
|
|
|
|
|
|
|
|
|
@staticmethod
|
2020-08-20 08:23:24 +00:00
|
|
|
|
def fillComboBox(cb, realtime=False):
|
2018-04-23 07:29:21 +00:00
|
|
|
|
"""
|
|
|
|
|
Fill TimeWeightings to a combobox
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
cb: QComboBox to fill
|
|
|
|
|
"""
|
|
|
|
|
cb.clear()
|
2020-08-20 08:23:24 +00:00
|
|
|
|
if realtime:
|
|
|
|
|
types = TimeWeighting.types_realtime
|
|
|
|
|
defindex = TimeWeighting.default_index_realtime
|
2020-02-29 17:07:46 +00:00
|
|
|
|
else:
|
2020-08-20 08:23:24 +00:00
|
|
|
|
types = TimeWeighting.types_all
|
|
|
|
|
defindex = TimeWeighting.default_index
|
2020-02-29 17:07:46 +00:00
|
|
|
|
for tw in types:
|
2018-05-02 14:29:53 +00:00
|
|
|
|
cb.addItem(tw[1], tw)
|
2020-08-20 08:23:24 +00:00
|
|
|
|
|
|
|
|
|
cb.setCurrentIndex(defindex)
|
2018-04-23 07:29:21 +00:00
|
|
|
|
|
2020-01-24 19:44:12 +00:00
|
|
|
|
@staticmethod
|
2018-04-23 07:29:21 +00:00
|
|
|
|
def getCurrent(cb):
|
2020-08-20 08:23:24 +00:00
|
|
|
|
if cb.count() == len(TimeWeighting.types_realtime):
|
|
|
|
|
return TimeWeighting.types_realtime[cb.currentIndex()]
|
|
|
|
|
else:
|
|
|
|
|
return TimeWeighting.types_all[cb.currentIndex()]
|
2018-04-23 07:29:21 +00:00
|
|
|
|
|
2020-05-28 19:26:30 +00:00
|
|
|
|
|
2018-04-23 07:29:21 +00:00
|
|
|
|
class FreqWeighting:
|
|
|
|
|
"""
|
|
|
|
|
Frequency weighting types
|
|
|
|
|
"""
|
2018-05-02 14:29:53 +00:00
|
|
|
|
A = ('A', 'A-weighting')
|
|
|
|
|
C = ('C', 'C-weighting')
|
|
|
|
|
Z = ('Z', 'Z-weighting')
|
|
|
|
|
types = (A, C, Z)
|
2020-01-24 19:44:12 +00:00
|
|
|
|
default = A
|
|
|
|
|
default_index = 0
|
2018-04-23 07:29:21 +00:00
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def fillComboBox(cb):
|
|
|
|
|
"""
|
|
|
|
|
Fill FreqWeightings to a combobox
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
cb: QComboBox to fill
|
|
|
|
|
"""
|
|
|
|
|
cb.clear()
|
|
|
|
|
for fw in FreqWeighting.types:
|
2018-05-02 14:29:53 +00:00
|
|
|
|
cb.addItem(fw[1], fw)
|
2020-01-24 19:44:12 +00:00
|
|
|
|
cb.setCurrentIndex(FreqWeighting.default_index)
|
2018-04-23 07:29:21 +00:00
|
|
|
|
|
2020-01-24 19:44:12 +00:00
|
|
|
|
@staticmethod
|
2018-04-23 07:29:21 +00:00
|
|
|
|
def getCurrent(cb):
|
|
|
|
|
return FreqWeighting.types[cb.currentIndex()]
|
|
|
|
|
|
2020-05-28 19:26:30 +00:00
|
|
|
|
|
2018-05-02 14:29:53 +00:00
|
|
|
|
def getTime(fs, N, start=0):
|
2018-04-23 07:29:21 +00:00
|
|
|
|
"""
|
|
|
|
|
Return a time array for given number of points and sampling frequency.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
fs: Sampling frequency [Hz]
|
|
|
|
|
N: Number of time samples
|
|
|
|
|
start: Optional start ofset in number of samples
|
|
|
|
|
"""
|
2019-10-27 13:19:26 +00:00
|
|
|
|
assert N > 0 and fs > 0
|
|
|
|
|
return np.linspace(start, start + N/fs, N, endpoint=False)
|
2018-04-23 07:29:21 +00:00
|
|
|
|
|
2020-05-28 19:26:30 +00:00
|
|
|
|
|
2018-04-23 07:29:21 +00:00
|
|
|
|
def getFreq(fs, nfft):
|
|
|
|
|
"""
|
|
|
|
|
return an array of frequencies for single-sided spectra
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
fs: Sampling frequency [Hz]
|
|
|
|
|
nfft: Fft length (int)
|
|
|
|
|
"""
|
2018-05-02 14:29:53 +00:00
|
|
|
|
df = fs/nfft # frequency resolution
|
|
|
|
|
K = nfft//2+1 # number of frequency bins
|
2018-04-23 07:29:21 +00:00
|
|
|
|
return np.linspace(0, (K-1)*df, K)
|