Updated exporting to wav file, NEEDS more testing. Added progress function to signal generator, which returns None by default

This commit is contained in:
Anne de Jong 2020-08-20 10:23:24 +02:00
parent 683c0b6566
commit 0a8949e0cf
4 changed files with 89 additions and 51 deletions

View File

@ -58,13 +58,30 @@ class AvStream:
self.input_sensitivity += daqconfig.getEnabledInputChannelSensitivities() self.input_sensitivity += daqconfig.getEnabledInputChannelSensitivities()
self.input_sensitivity = np.asarray(self.input_sensitivity) 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 += [ self.input_channel_names += [
ch.channel_name for ch in daqconfig.getEnabledInputChannels()] ch.channel_name for ch in daqconfig.getEnabledInputChannels()]
self.input_samplerate = float(daqconfig.en_input_rate) self.input_samplerate = float(daqconfig.en_input_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_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 # Counters for the number of frames that have been coming in
self._aframectr = Atomic(0) self._aframectr = Atomic(0)
self._vframectr = Atomic(0) self._vframectr = Atomic(0)
@ -90,16 +107,6 @@ class AvStream:
self._rtaudio = RtAudio(daqconfig.api) self._rtaudio = RtAudio(daqconfig.api)
self.blocksize = self._rtaudio.openStream(self) 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): def close(self):
self._rtaudio.closeStream() self._rtaudio.closeStream()

View File

@ -233,13 +233,14 @@ class TimeWeighting:
slow = (1.0, 'Slow (1.0 s)') slow = (1.0, 'Slow (1.0 s)')
tens = (10., '10 s') tens = (10., '10 s')
infinite = (0, 'Infinite') 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) types_all = (none, uufast, ufast, fast, slow, tens, infinite)
default = fast default = fast
default_index = 3 default_index = 3
default_index_realtime = 1
@staticmethod @staticmethod
def fillComboBox(cb, all_=False): def fillComboBox(cb, realtime=False):
""" """
Fill TimeWeightings to a combobox Fill TimeWeightings to a combobox
@ -247,16 +248,22 @@ class TimeWeighting:
cb: QComboBox to fill cb: QComboBox to fill
""" """
cb.clear() cb.clear()
if all_: if realtime:
types = TimeWeighting.types_all types = TimeWeighting.types_realtime
defindex = TimeWeighting.default_index_realtime
else: else:
types = TimeWeighting.types types = TimeWeighting.types_all
defindex = TimeWeighting.default_index
for tw in types: for tw in types:
cb.addItem(tw[1], tw) cb.addItem(tw[1], tw)
cb.setCurrentIndex(TimeWeighting.default_index)
cb.setCurrentIndex(defindex)
@staticmethod @staticmethod
def getCurrent(cb): def getCurrent(cb):
if cb.count() == len(TimeWeighting.types_realtime):
return TimeWeighting.types_realtime[cb.currentIndex()]
else:
return TimeWeighting.types_all[cb.currentIndex()] return TimeWeighting.types_all[cb.currentIndex()]

View File

@ -54,7 +54,7 @@ class BlockIter:
"""Initialize a BlockIter object. """Initialize a BlockIter object.
Args: 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.i = 0
self.nblocks = f['audio'].shape[0] self.nblocks = f['audio'].shape[0]
@ -98,6 +98,7 @@ def scaleBlockSens(block, sens):
sensitivity: array of sensitivity coeficients for sensitivity: array of sensitivity coeficients for
each channel each channel
""" """
sens = np.asarray(sens)
assert sens.ndim == 1 assert sens.ndim == 1
assert sens.size == block.shape[1] assert sens.size == block.shape[1]
if np.issubdtype(block.dtype.type, np.integer): if np.issubdtype(block.dtype.type, np.integer):
@ -150,6 +151,7 @@ class Measurement:
self.nblocks, self.blocksize, self.nchannels = f['audio'].shape self.nblocks, self.blocksize, self.nchannels = f['audio'].shape
dtype = f['audio'].dtype dtype = f['audio'].dtype
self.dtype = dtype
self.sampwidth = getSampWidth(dtype) self.sampwidth = getSampWidth(dtype)
self.samplerate = f.attrs['samplerate'] self.samplerate = f.attrs['samplerate']
@ -267,20 +269,44 @@ class Measurement:
self._prms = np.sqrt(pms) self._prms = np.sqrt(pms)
return self._prms return self._prms
def praw(self, block=None): def rawData(self, block=None, channel=None):
"""Returns the uncalibrated acoustic pressure signal, converted to """Returns the raw signal, without any transformations applied
floating point acoustic pressure values [Pa]."""
args:
block: If specified a certain block is returned
"""
if block is not None: if block is not None:
with self.file() as f: with self.file() as f:
if channel is not None:
blocks = f['audio'][block][:, [channel]]
else:
blocks = f['audio'][block] blocks = f['audio'][block]
else: else:
blocks = [] blocks = []
with self.file() as f: with self.file() as f:
for block in self.iterBlocks(f): for block in self.iterBlocks(f):
if channel is not None:
blocks.append(block[:, [channel]])
else:
blocks.append(block) blocks.append(block)
blocks = np.asarray(blocks) 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) # Apply scaling (sensitivity, integer -> float)
blocks = self.scaleBlock(blocks) blocks = self.scaleBlock(blocks)
@ -345,9 +371,10 @@ class Measurement:
f.attrs['sensitivity'] = sens f.attrs['sensitivity'] = sens
self._sens = 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 """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: Args:
fn: If given, this will be the filename to write to. If the fn: If given, this will be the filename to write to. If the
@ -366,26 +393,21 @@ class Measurement:
fn = self.fn fn = self.fn
fn = os.path.splitext(fn)[0] fn = os.path.splitext(fn)[0]
if '.wav' not in fn[-4:]: if os.path.splitext(fn)[1] != '.wav':
fn += '.wav' fn += '.wav'
if os.path.exists(fn) and not force: if os.path.exists(fn) and not force:
raise RuntimeError(f'File already exists: {fn}') raise RuntimeError(f'File already exists: {fn}')
with self.file() as f: data = self.rawData()
audio = f['audio']
oldsampwidth = getSampWidth(audio.dtype)
max_ = 1.
if normalize: if normalize:
# Find maximum value maxabs = np.max(np.abs(data), axis=0)
for block in self.iterBlocks(f): data /= maxabs[np.newaxis, :]
blockmax = np.max(np.abs(block))
max_ = blockmax if blockmax > max_ else max_ if newsampwidth is not None:
# Scale with maximum value only if we have a nonzero maximum value. # Convert to floats, then to new sample width
if max_ == 0.: data = scaleBlockSens(data, self.sensitivity**0)
max_ = 1.
if newsampwidth == 2: if newsampwidth == 2:
newtype = np.int16 newtype = np.int16
@ -394,17 +416,12 @@ class Measurement:
else: else:
raise ValueError('Invalid sample width, should be 2 or 4') raise ValueError('Invalid sample width, should be 2 or 4')
scalefac = 2**(8*(newsampwidth-oldsampwidth)) scalefac = 2**(8*(newsampwidth-1))-1
if normalize or isinstance(audio.dtype, float):
scalefac *= .01*max 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 @staticmethod
def fromtxt(fn, def fromtxt(fn,

View File

@ -673,6 +673,13 @@ cdef class Siggen:
return output return output
def progress(self):
"""
TODO: Should be implemented to return the current position in the
generator.
"""
return None
@staticmethod @staticmethod
def sineWave(d fs,d freq,d level_dB): def sineWave(d fs,d freq,d level_dB):