From 0a8949e0cf675705198be83b4a0b5ec8c419bd8f Mon Sep 17 00:00:00 2001 From: "J.A. de Jong - Redu-Sone B.V., ASCEE V.O.F" Date: Thu, 20 Aug 2020 10:23:24 +0200 Subject: [PATCH] Updated exporting to wav file, NEEDS more testing. Added progress function to signal generator, which returns None by default --- lasp/lasp_avstream.py | 29 ++++++++------ lasp/lasp_common.py | 21 ++++++---- lasp/lasp_measurement.py | 83 ++++++++++++++++++++++++---------------- lasp/wrappers.pyx | 7 ++++ 4 files changed, 89 insertions(+), 51 deletions(-) diff --git a/lasp/lasp_avstream.py b/lasp/lasp_avstream.py index 3abd4fb..c784e8b 100644 --- a/lasp/lasp_avstream.py +++ b/lasp/lasp_avstream.py @@ -58,12 +58,29 @@ class AvStream: self.input_sensitivity += daqconfig.getEnabledInputChannelSensitivities() self.input_sensitivity = np.asarray(self.input_sensitivity) + # Fill in numpy data type, and sample width + self.input_numpy_dtype = get_numpy_dtype_from_format_string( + daqconfig.en_input_sample_format) + + self.input_sampwidth = get_sampwidth_from_format_string( + daqconfig.en_input_sample_format) self.input_channel_names += [ ch.channel_name for ch in daqconfig.getEnabledInputChannels()] self.input_samplerate = float(daqconfig.en_input_rate) - self.output_samplerate = float(daqconfig.en_output_rate) + if self.duplex_mode: + self.output_samplerate = float(daqconfig.en_input_rate) + self.output_sampwidth = self.input_sampwidth + self.output_numpy_dtype = self.input_numpy_dtype + else: + self.output_samplerate = float(daqconfig.en_output_rate) + + self.output_sampwidth = get_sampwidth_from_format_string( + daqconfig.en_output_sample_format) + + self.output_numpy_dtype = get_numpy_dtype_from_format_string( + daqconfig.en_output_sample_format) # Counters for the number of frames that have been coming in self._aframectr = Atomic(0) @@ -90,16 +107,6 @@ class AvStream: self._rtaudio = RtAudio(daqconfig.api) self.blocksize = self._rtaudio.openStream(self) - # Fill in numpy data type, and sample width - self.input_numpy_dtype = get_numpy_dtype_from_format_string( - daqconfig.en_input_sample_format) - self.output_numpy_dtype = get_numpy_dtype_from_format_string( - daqconfig.en_output_sample_format) - - self.input_sampwidth = get_sampwidth_from_format_string( - daqconfig.en_input_sample_format) - self.output_sampwidth = get_sampwidth_from_format_string( - daqconfig.en_output_sample_format) def close(self): self._rtaudio.closeStream() diff --git a/lasp/lasp_common.py b/lasp/lasp_common.py index 3de2052..e3a7c58 100644 --- a/lasp/lasp_common.py +++ b/lasp/lasp_common.py @@ -233,13 +233,14 @@ class TimeWeighting: slow = (1.0, 'Slow (1.0 s)') tens = (10., '10 s') infinite = (0, 'Infinite') - types = (none, uufast, ufast, fast, slow, tens) + types_realtime = (ufast, fast, slow, tens, infinite) types_all = (none, uufast, ufast, fast, slow, tens, infinite) default = fast default_index = 3 + default_index_realtime = 1 @staticmethod - def fillComboBox(cb, all_=False): + def fillComboBox(cb, realtime=False): """ Fill TimeWeightings to a combobox @@ -247,17 +248,23 @@ class TimeWeighting: cb: QComboBox to fill """ cb.clear() - if all_: - types = TimeWeighting.types_all + if realtime: + types = TimeWeighting.types_realtime + defindex = TimeWeighting.default_index_realtime else: - types = TimeWeighting.types + types = TimeWeighting.types_all + defindex = TimeWeighting.default_index for tw in types: cb.addItem(tw[1], tw) - cb.setCurrentIndex(TimeWeighting.default_index) + + cb.setCurrentIndex(defindex) @staticmethod def getCurrent(cb): - return TimeWeighting.types_all[cb.currentIndex()] + if cb.count() == len(TimeWeighting.types_realtime): + return TimeWeighting.types_realtime[cb.currentIndex()] + else: + return TimeWeighting.types_all[cb.currentIndex()] class FreqWeighting: diff --git a/lasp/lasp_measurement.py b/lasp/lasp_measurement.py index 09fa710..e2fced4 100644 --- a/lasp/lasp_measurement.py +++ b/lasp/lasp_measurement.py @@ -54,7 +54,7 @@ class BlockIter: """Initialize a BlockIter object. Args: - faudio: Audio dataset in the h5 file, accessed as f['audio'] + f: Audio dataset in the h5 file, accessed as f['audio'] """ self.i = 0 self.nblocks = f['audio'].shape[0] @@ -98,6 +98,7 @@ def scaleBlockSens(block, sens): sensitivity: array of sensitivity coeficients for each channel """ + sens = np.asarray(sens) assert sens.ndim == 1 assert sens.size == block.shape[1] if np.issubdtype(block.dtype.type, np.integer): @@ -150,6 +151,7 @@ class Measurement: self.nblocks, self.blocksize, self.nchannels = f['audio'].shape dtype = f['audio'].dtype + self.dtype = dtype self.sampwidth = getSampWidth(dtype) self.samplerate = f.attrs['samplerate'] @@ -267,20 +269,44 @@ class Measurement: self._prms = np.sqrt(pms) return self._prms - def praw(self, block=None): - """Returns the uncalibrated acoustic pressure signal, converted to - floating point acoustic pressure values [Pa].""" + def rawData(self, block=None, channel=None): + """Returns the raw signal, without any transformations applied + + args: + block: If specified a certain block is returned + + """ if block is not None: with self.file() as f: - blocks = f['audio'][block] + if channel is not None: + blocks = f['audio'][block][:, [channel]] + else: + blocks = f['audio'][block] else: blocks = [] with self.file() as f: for block in self.iterBlocks(f): - blocks.append(block) + if channel is not None: + blocks.append(block[:, [channel]]) + else: + blocks.append(block) + blocks = np.asarray(blocks) - blocks = blocks.reshape(self.nblocks * self.blocksize, - self.nchannels) + + if channel is None: + blocks = blocks.reshape((self.nblocks * self.blocksize, + self.nchannels)) + else: + blocks = blocks.reshape((self.nblocks * self.blocksize, + 1)) + return blocks + + def praw(self, block=None): + """Returns the uncalibrated acoustic pressure signal, but the + sensitivity is applied, converted to floating point acoustic + pressure values [Pa].""" + + blocks = self.rawData(block) # Apply scaling (sensitivity, integer -> float) blocks = self.scaleBlock(blocks) @@ -345,9 +371,10 @@ class Measurement: f.attrs['sensitivity'] = sens self._sens = sens - def exportAsWave(self, fn=None, force=False, newsampwidth=2, normalize=True): + def exportAsWave(self, fn=None, force=False, newsampwidth=None, normalize=True): """Export measurement file as wave. In case the measurement data is - stored as floats, the values are scaled between 0 and 1. + stored as floats, the values are scaled to the proper integer (PCM) + data format. Args: fn: If given, this will be the filename to write to. If the @@ -366,26 +393,21 @@ class Measurement: fn = self.fn fn = os.path.splitext(fn)[0] - if '.wav' not in fn[-4:]: + if os.path.splitext(fn)[1] != '.wav': fn += '.wav' if os.path.exists(fn) and not force: raise RuntimeError(f'File already exists: {fn}') - with self.file() as f: + data = self.rawData() - audio = f['audio'] - oldsampwidth = getSampWidth(audio.dtype) + if normalize: + maxabs = np.max(np.abs(data), axis=0) + data /= maxabs[np.newaxis, :] - max_ = 1. - if normalize: - # Find maximum value - for block in self.iterBlocks(f): - blockmax = np.max(np.abs(block)) - max_ = blockmax if blockmax > max_ else max_ - # Scale with maximum value only if we have a nonzero maximum value. - if max_ == 0.: - max_ = 1. + if newsampwidth is not None: + # Convert to floats, then to new sample width + data = scaleBlockSens(data, self.sensitivity**0) if newsampwidth == 2: newtype = np.int16 @@ -394,17 +416,12 @@ class Measurement: else: raise ValueError('Invalid sample width, should be 2 or 4') - scalefac = 2**(8*(newsampwidth-oldsampwidth)) - if normalize or isinstance(audio.dtype, float): - scalefac *= .01*max + scalefac = 2**(8*(newsampwidth-1))-1 + + data = (data*scalefac).astype(newtype) + + wavfile.write(fn, self.samplerate, data) - with wave.open(fn, 'w') as wf: - wf.setparams((self.nchannels, self.sampwidth, - self.samplerate, 0, 'NONE', 'NONE')) - for block in self.iterBlocks(f): - # Convert block to integral data type - block = (block*scalefac).astype(newtype) - wf.writeframes(np.asfortranarray(block).tobytes()) @staticmethod def fromtxt(fn, diff --git a/lasp/wrappers.pyx b/lasp/wrappers.pyx index 6d89325..2263a1c 100644 --- a/lasp/wrappers.pyx +++ b/lasp/wrappers.pyx @@ -673,6 +673,13 @@ cdef class Siggen: return output + def progress(self): + """ + TODO: Should be implemented to return the current position in the + generator. + + """ + return None @staticmethod def sineWave(d fs,d freq,d level_dB):