lasp/lasp/lasp_common.py

316 lines
7.7 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import platform
import shelve
import sys
import appdirs
import numpy as np
from .wrappers import Window as wWindow
from collections import namedtuple
from dataclasses import dataclass
from dataclasses_json import dataclass_json
"""
Common definitions used throughout the code.
"""
__all__ = [
'P_REF', 'FreqWeighting', 'TimeWeighting', 'getTime', 'getFreq',
'lasp_shelve', 'this_lasp_shelve', 'W_REF', 'U_REF', 'I_REF', 'dBFS_REF',
]
# 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 0dB 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
@dataclass_json
@dataclass
class Qty:
name: str
unit_name: str
unit_symb: str
level_unit: str
level_ref_name: str
level_ref_value: str
class SIQtys:
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,)
)
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',
unit_name='Volt',
unit_symb='V',
level_unit=('dBV',), # dBV
level_ref_name=('1V',),
level_ref_value=(1.0,),
)
types = (AP, V, N)
default = N
default_index = 0
@staticmethod
def fillComboBox(cb):
"""
Fill FreqWeightings to a combobox
Args:
cb: QComboBox to fill
"""
cb.clear()
for ty in SIQtys.types:
cb.addItem(f'{ty.unit_name}')
cb.setCurrentIndex(SIQtys.default_index)
@staticmethod
def getCurrent(cb):
return SIQtys.types[cb.currentIndex()]
lasp_appdir = appdirs.user_data_dir('Lasp', 'ASCEE')
if not os.path.exists(lasp_appdir):
try:
os.makedirs(lasp_appdir, exist_ok=True)
except:
print('Fatal error: could not create application directory')
sys.exit(1)
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
def __enter__(self):
self.incref()
return self
def store(self, key, val):
self._shelve[key] = val
def deleteIfPresent(self, key):
try:
del self._shelve[key]
except:
pass
def printAllKeys(self):
print(list(self.shelve.keys()))
def incref(self):
if self.shelve is None:
assert self.refcount == 0
self.shelve = shelve.open(self.shelve_fn())
self.refcount += 1
def decref(self):
self.refcount -= 1
if self.refcount == 0:
self.shelve.close()
self.shelve = None
def __exit__(self, type, value, traceback):
self.decref()
class lasp_shelve(Shelve):
_refcount = 0
_shelve = None
@property
def refcount(self):
return lasp_shelve._refcount
@refcount.setter
def refcount(self, val):
lasp_shelve._refcount = val
@property
def shelve(self):
return lasp_shelve._shelve
@shelve.setter
def shelve(self, shelve):
lasp_shelve._shelve = shelve
def shelve_fn(self):
return os.path.join(lasp_appdir, 'config.shelve')
class this_lasp_shelve(Shelve):
_refcount = 0
_shelve = None
@property
def refcount(self):
return this_lasp_shelve._refcount
@refcount.setter
def refcount(self, val):
this_lasp_shelve._refcount = val
@property
def shelve(self):
return this_lasp_shelve._shelve
@shelve.setter
def shelve(self, shelve):
this_lasp_shelve._shelve = shelve
def shelve_fn(self):
node = platform.node()
return os.path.join(lasp_appdir, f'{node}_config.shelve')
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()]
class TimeWeighting:
none = (-1, 'Raw (no time weighting)')
uufast = (1e-4, '0.1 ms')
ufast = (35e-3, 'Impulse (35 ms)')
fast = (0.125, 'Fast (0.125 s)')
slow = (1.0, 'Slow (1.0 s)')
tens = (10., '10 s')
infinite = (0, 'Infinite')
types = (none, uufast, ufast, fast, slow, tens)
types_all = (none, uufast, ufast, fast, slow, tens, infinite)
default = fast
default_index = 3
@staticmethod
def fillComboBox(cb, all_=False):
"""
Fill TimeWeightings to a combobox
Args:
cb: QComboBox to fill
"""
cb.clear()
if all_:
types = TimeWeighting.types_all
else:
types = TimeWeighting.types
for tw in types:
cb.addItem(tw[1], tw)
cb.setCurrentIndex(TimeWeighting.default_index)
@staticmethod
def getCurrent(cb):
return TimeWeighting.types_all[cb.currentIndex()]
class FreqWeighting:
"""
Frequency weighting types
"""
A = ('A', 'A-weighting')
C = ('C', 'C-weighting')
Z = ('Z', 'Z-weighting')
types = (A, C, Z)
default = A
default_index = 0
@staticmethod
def fillComboBox(cb):
"""
Fill FreqWeightings to a combobox
Args:
cb: QComboBox to fill
"""
cb.clear()
for fw in FreqWeighting.types:
cb.addItem(fw[1], fw)
cb.setCurrentIndex(FreqWeighting.default_index)
@staticmethod
def getCurrent(cb):
return FreqWeighting.types[cb.currentIndex()]
def getTime(fs, N, start=0):
"""
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
"""
assert N > 0 and fs > 0
return np.linspace(start, start + N/fs, N, endpoint=False)
def getFreq(fs, nfft):
"""
return an array of frequencies for single-sided spectra
Args:
fs: Sampling frequency [Hz]
nfft: Fft length (int)
"""
df = fs/nfft # frequency resolution
K = nfft//2+1 # number of frequency bins
return np.linspace(0, (K-1)*df, K)