diff --git a/lasp/__init__.py b/lasp/__init__.py index becea65..c0fadc5 100644 --- a/lasp/__init__.py +++ b/lasp/__init__.py @@ -1,11 +1,10 @@ -from .lasp_atomic import * -from .lasp_avstream import * from .lasp_common import * +from .lasp_avstream import * +from .wrappers import * +from .lasp_atomic import * from .lasp_imptube import * from .lasp_measurement import * from .lasp_octavefilter import * from .lasp_slm import * from .lasp_siggen import * from .lasp_weighcal import * -from .wrappers import * -from .device import AvType diff --git a/lasp/c/lasp_siggen.c b/lasp/c/lasp_siggen.c index d9610b4..766af76 100644 --- a/lasp/c/lasp_siggen.c +++ b/lasp/c/lasp_siggen.c @@ -332,6 +332,13 @@ us Siggen_getN(const Siggen* siggen) { feTRACE(15); return 0; } +void Siggen_setLevel(Siggen* siggen, const d new_level_dB) { + fsTRACE(15); + + siggen->level_amp = d_pow(10, new_level_dB/20); + + feTRACE(15); +} void Siggen_free(Siggen* siggen) { fsTRACE(15); diff --git a/lasp/c/lasp_siggen.h b/lasp/c/lasp_siggen.h index d5ec290..52e7862 100644 --- a/lasp/c/lasp_siggen.h +++ b/lasp/c/lasp_siggen.h @@ -46,6 +46,14 @@ Siggen* Siggen_Sinewave_create(const d fs,const d freq,const d level_dB); */ Siggen* Siggen_Noise_create(const d fs, const d level_dB, Sosfilterbank* colorfilter); +/** + * Set the level of the signal generator + * @param[in] Siggen* Signal generator handle + * + * @param[in] new_level_dB The new level, in dBFS + */ +void Siggen_setLevel(Siggen*, const d new_level_dB); + /** * Obtain the repetition period for a periodic excitation. diff --git a/lasp/device/__init__.py b/lasp/device/__init__.py index 7bd944a..6fde552 100644 --- a/lasp/device/__init__.py +++ b/lasp/device/__init__.py @@ -1,6 +1,4 @@ -#!/usr/bin/python3 from .lasp_device_common import * -from .lasp_daq import * from .lasp_deviceinfo import * from .lasp_daqconfig import * from .lasp_daq import * diff --git a/lasp/device/lasp_daq.pyx b/lasp/device/lasp_daq.pyx index c05854e..8b1039c 100644 --- a/lasp/device/lasp_daq.pyx +++ b/lasp/device/lasp_daq.pyx @@ -4,7 +4,7 @@ from .lasp_daqconfig cimport DaqConfiguration from cpython.ref cimport PyObject,Py_INCREF, Py_DECREF import numpy as np -from .lasp_device_common import AvType +from ..lasp_common import AvType __all__ = ['Daq'] diff --git a/lasp/device/lasp_daqconfig.pyx b/lasp/device/lasp_daqconfig.pyx index b1d382f..a34063d 100644 --- a/lasp/device/lasp_daqconfig.pyx +++ b/lasp/device/lasp_daqconfig.pyx @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 # -*- coding: utf-8 -*- """! Author: J.A. de Jong - ASCEE diff --git a/lasp/device/lasp_device_common.py b/lasp/device/lasp_device_common.py index 5cedd8e..40c9bde 100644 --- a/lasp/device/lasp_device_common.py +++ b/lasp/device/lasp_device_common.py @@ -1,17 +1,10 @@ -__all__ = ['AvType', 'DaqChannel'] +__all__ = ['DaqChannel'] from ..lasp_common import Qty, SIQtys from dataclasses import dataclass, field from dataclasses_json import dataclass_json from typing import List import json -class AvType: - """Specificying the type of data, for adding and removing callbacks from - the stream.""" - audio_input = 1 - audio_output = 2 - video = 4 - @dataclass_json @dataclass class DaqChannel: diff --git a/lasp/device/lasp_deviceinfo.pyx b/lasp/device/lasp_deviceinfo.pyx index 2ead824..c5e991b 100644 --- a/lasp/device/lasp_deviceinfo.pyx +++ b/lasp/device/lasp_deviceinfo.pyx @@ -1,3 +1,12 @@ +# -*- coding: utf-8 -*- +"""! +Author: J.A. de Jong - ASCEE + +Description: + +DeviceInfo C++ object wrapper + +""" __all__ = ['DeviceInfo'] diff --git a/lasp/lasp_avstream.py b/lasp/lasp_avstream.py index 70bfc87..79a0664 100644 --- a/lasp/lasp_avstream.py +++ b/lasp/lasp_avstream.py @@ -1,20 +1,15 @@ -#!/usr/bin/env python3.6 # -*- coding: utf-8 -*- """ Description: Read data from image stream and record sound at the same time """ #import cv2 as cv from .lasp_atomic import Atomic +from .lasp_common import AvType +from .device import (Daq, DeviceInfo, DaqConfiguration) from threading import Thread, Lock import numpy as np - -class DAQConfiguration: - pass - import time -from .device import (Daq, DeviceInfo, - AvType, - ) + __all__ = ['AvStream'] @@ -28,7 +23,7 @@ class AvStream: def __init__(self, avtype: AvType, device: DeviceInfo, - daqconfig: DAQConfiguration, + daqconfig: DaqConfiguration, video=None): """Open a stream for audio in/output and video input. For audio output, by default all available channels are opened for outputting data. diff --git a/lasp/lasp_common.py b/lasp/lasp_common.py index 2125b73..1fb8249 100644 --- a/lasp/lasp_common.py +++ b/lasp/lasp_common.py @@ -20,6 +20,7 @@ __all__ = [ 'P_REF', 'FreqWeighting', 'TimeWeighting', 'getTime', 'getFreq', 'Qty', 'SIQtys', 'lasp_shelve', 'this_lasp_shelve', 'W_REF', 'U_REF', 'I_REF', 'dBFS_REF', + 'AvType' ] # Reference sound pressure level @@ -42,6 +43,14 @@ U_REF = 5e-8 # 50 nano meter / s # hence this is the reference level as specified below. dBFS_REF = 0.5*2**0.5 # Which level would be -3.01 dBFS +class AvType: + """Specificying the type of data, for adding and removing callbacks from + the stream.""" + audio_input = 1 + audio_output = 2 + video = 4 + + @dataclass_json @dataclass class Qty: diff --git a/lasp/lasp_imptube.py b/lasp/lasp_imptube.py index 56d471d..f32ec44 100644 --- a/lasp/lasp_imptube.py +++ b/lasp/lasp_imptube.py @@ -11,6 +11,8 @@ from .lasp_measurement import Measurement from numpy import pi, sqrt, exp import numpy as np from scipy.interpolate import UnivariateSpline +from lrftubes import PrsDuct +from functools import lru_cache class TwoMicImpedanceTube: def __init__(self, mnormal: Measurement, @@ -22,6 +24,8 @@ class TwoMicImpedanceTube: fu: float = None, periodic_method=False, mat= Air(), + D_imptube = 50e-3, + thermoviscous = True, **kwargs): """ @@ -60,6 +64,9 @@ class TwoMicImpedanceTube: kmax = ksmax/s self.fu = kmax*mat.c0/2/pi + self.thermoviscous = thermoviscous + self.D_imptube = D_imptube + self.periodic_method = periodic_method self.channels = [kwargs.pop('chan0', 0), kwargs.pop('chan1', 1)] # Compute calibration correction @@ -82,8 +89,9 @@ class TwoMicImpedanceTube: # Calibration correction factor # self.K = 0*self.freq + 1.0 K = sqrt(C2[:,0,1]*C1[:,0,0]/(C2[:,1,1]*C1[:,1,0])) - self.K = UnivariateSpline(self.freq, K.real)(self.freq) +\ - 1j*UnivariateSpline(self.freq, K.imag)(self.freq) + # self.K = UnivariateSpline(self.freq, K.real)(self.freq) +\ + # 1j*UnivariateSpline(self.freq, K.imag)(self.freq) + self.K = K def cut_to_limits(self, ar): return ar[self.il:self.ul] @@ -94,6 +102,7 @@ class TwoMicImpedanceTube: """ return self.cut_to_limits(self.freq) + @lru_cache def G_AB(self, meas): if meas is self.mnormal: C = self.C1 diff --git a/lasp/lasp_siggen.py b/lasp/lasp_siggen.py index 0144d7f..9f8fcd5 100644 --- a/lasp/lasp_siggen.py +++ b/lasp/lasp_siggen.py @@ -14,8 +14,10 @@ from typing import Tuple import numpy as np from .filter import PinkNoise +from .lasp_octavefilter import SosOctaveFilterBank, SosThirdOctaveFilterBank +from .filter import OctaveBankDesigner, PinkNoise, ThirdOctaveBankDesigner from .lasp_avstream import AvStream, AvType -from .wrappers import Siggen as pyxSiggen +from .wrappers import Siggen as pyxSiggen, Equalizer QUEUE_BUFFER_TIME = 0.3 # The amount of time used in the queues for buffering # of data, larger is more stable, but also enlarges latency @@ -89,7 +91,7 @@ class SiggenData: def siggenFcn(siggendata: SiggenData, nblocks_buffer: int, - queues: list + dataq: mp.Queue, pipe ): """ Main function running in a different process, is responsible for generating @@ -100,12 +102,10 @@ def siggenFcn(siggendata: SiggenData, nblocks_buffer: int, nframes_per_block = siggendata.nframes_per_block level_dB = siggendata.level_dB dtype = siggendata.dtype - eq = None signaltype = siggendata.signaltype signaltypedata = siggendata.signaltypedata - dataq, msgq, statusq = queues def generate(siggen, eq): """ @@ -119,6 +119,31 @@ def siggenFcn(siggendata: SiggenData, nblocks_buffer: int, signal *= 2 ** (bitdepth_fixed - 1) - 1 dataq.put(signal.astype(dtype)) + def createEqualizer(eqdata): + """ + Create an equalizer object from equalizer data + + Args: + eqdata: dictionary containing equalizer data. TODO: document the + requiring fields. + """ + if eqdata is None: + return None + eq_type = eqdata['type'] + eq_levels = eqdata['levels'] + + if eq_type == 'three': + fb = SosThirdOctaveFilterBank(fs) + elif eq_type == 'one': + fb = SosOctaveFilterBank(fs) + + eq = Equalizer(fb._fb) + if eq_levels is not None: + eq.setLevels(eq_levels) + return eq + + eq = createEqualizer(siggendata.eqdata) + def newSiggen(): """ Create a signal generator based on parameters specified in global @@ -156,14 +181,14 @@ def siggenFcn(siggendata: SiggenData, nblocks_buffer: int, siggen = newSiggen() except Exception as e: - statusq.put((SiggenMessage.error, str(e))) + pipe.send((SiggenMessage.error, str(e))) return 1 finally: - statusq.put((SiggenMessage.done, None)) + pipe.send((SiggenMessage.done, None)) while True: - msg, data = msgq.get() + msg, data = pipe.recv() if msg == SiggenMessage.stop: logging.debug("Signal generator caught 'stop' message. Exiting.") return 0 @@ -174,14 +199,17 @@ def siggenFcn(siggendata: SiggenData, nblocks_buffer: int, # Generate new data and put it in the queue! generate(siggen, eq) except SiggenWorkerDone: - statusq.put(SiggenMessage.done) + pipe.send(SiggenMessage.done) return 0 elif msg == SiggenMessage.adjustVolume: logging.debug(f"Signal generator caught 'adjustVolume' message. New volume = {level_dB:.1f} dB FS") level_dB = data - siggen = newSiggen() + siggen.setLevel(level_dB) + elif msg == SiggenMessage.newEqSettings: + eqdata = data + eq = createEqualizer(eqdata) else: - statusq.put( + pipe.send( SiggenMessage.error, "BUG: Generator caught unknown message. Quiting" ) return 1 @@ -201,11 +229,12 @@ class Siggen: ) qs = [mp.Queue() for i in range(3)] - self.dataq, self.msgq, self.statusq = qs + self.dataq = mp.Queue() + self.pipe, client_end = mp.Pipe(duplex=True) self.process = mp.Process( target=siggenFcn, - args=(siggendata, self.nblocks_buffer, qs), + args=(siggendata, self.nblocks_buffer, self.dataq, client_end), ) self.process.start() @@ -222,11 +251,14 @@ class Siggen: Args: new_level: The new level in [dBFS] """ - self.msgq.put((SiggenMessage.adjustVolume, new_level)) + self.pipe.send((SiggenMessage.adjustVolume, new_level)) + + def setEqData(self, eqdata): + self.pipe.send((SiggenMessage.newEqSettings, eqdata)) def handle_msgs(self): - while not self.statusq.empty(): - msg, data = self.statusq.get() + while self.pipe.poll(): + msg, data = self.pipe.recv() if msg == SiggenMessage.error: self.stop() raise RuntimeError( @@ -239,6 +271,9 @@ class Siggen: self.stop() def start(self): + if self.stopped: + raise RuntimeError('BUG: This Siggen object cannot be used again.') + self.stream.addCallback(self.streamCallback, AvType.audio_output) self.handle_msgs() @@ -249,9 +284,8 @@ class Siggen: self.stream.removeCallback(self.streamCallback, AvType.audio_output) while not self.dataq.empty(): self.dataq.get() - while not self.statusq.empty(): - self.statusq.get() - self.msgq.put((SiggenMessage.stop, None)) + self.pipe.send((SiggenMessage.stop, None)) + self.pipe.close() logging.debug('Joining siggen process') self.process.join() @@ -276,4 +310,4 @@ class Siggen: outdata[:, :] = 0 if self.dataq.qsize() < self.nblocks_buffer: - self.msgq.put((SiggenMessage.generate, None)) + self.pipe.send((SiggenMessage.generate, None)) diff --git a/lasp/wrappers.pyx b/lasp/wrappers.pyx index e9e6fa9..536c396 100644 --- a/lasp/wrappers.pyx +++ b/lasp/wrappers.pyx @@ -74,7 +74,7 @@ cdef extern from "lasp_python.h": __all__ = ['AvPowerSpectra', 'SosFilterBank', 'FilterBank', 'Siggen', 'sweep_flag_forward', 'sweep_flag_backward', 'sweep_flag_linear', 'sweep_flag_exponential', - 'load_fft_wisdom', 'store_fft_wisdom'] + 'load_fft_wisdom', 'store_fft_wisdom', 'Window'] setTracerLevel(15) @@ -635,6 +635,7 @@ cdef extern from "lasp_siggen.h": c_Siggen* Siggen_Sweep_create(d fs, d fl, d fu, d Ts,d Tq, us sweep_flags, d level_dB) + void Siggen_setLevel(c_Siggen*,d new_level_dB) us Siggen_getN(const c_Siggen*) void Siggen_genSignal(c_Siggen*, vd* samples) nogil void Siggen_free(c_Siggen*) @@ -659,6 +660,9 @@ cdef class Siggen: if self._siggen: Siggen_free(self._siggen) + def setLevel(self,d level_dB): + Siggen_setLevel(self._siggen, level_dB) + def genSignal(self, us nsamples): output = np.empty(nsamples, dtype=np.float) assert self._siggen != NULL