From 3ea745245fbcf5af232376dc3f97a3f111cafdd3 Mon Sep 17 00:00:00 2001 From: "J.A. de Jong - ASCEE" Date: Tue, 17 Dec 2019 14:09:45 +0100 Subject: [PATCH] First work in the direction of getting a recording running again --- CMakeLists.txt | 2 +- lasp/device/__init__.py | 8 +-- lasp/device/lasp_daqconfig.py | 98 ++++++++++++++++---------------- lasp/device/lasp_rtaudio.pyx | 104 ++++++++++++---------------------- lasp/lasp_avstream.py | 11 +++- lasp/lasp_common.py | 17 ++++-- 6 files changed, 112 insertions(+), 128 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6e971c6..1fcbc9f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,7 +115,7 @@ set(SETUP_PY "${CMAKE_CURRENT_BINARY_DIR}/setup.py") set(DEPS "${CMAKE_CURRENT_SOURCE_DIR}/*.py" "${CMAKE_CURRENT_SOURCE_DIR}/lasp/*.py" "wrappers" - "lasp_daqdevice") + "lasp_rtaudio") # ) set(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/build/timestamp") diff --git a/lasp/device/__init__.py b/lasp/device/__init__.py index fb04157..c33eb40 100644 --- a/lasp/device/__init__.py +++ b/lasp/device/__init__.py @@ -1,6 +1,4 @@ #!/usr/bin/python3 -__all__ = ['DAQDevice', 'DAQConfiguration', 'configs', 'query_devices', - 'roga_plugndaq', 'default_soundcard'] -from .lasp_daqdevice import DAQDevice, query_devices -from .lasp_daqconfig import (DAQConfiguration, configs, roga_plugndaq, - default_soundcard) +__all__ = ['DAQConfiguration'] +from .lasp_daqconfig import DAQConfiguration, DAQInputChannel +from .lasp_rtaudio import RtAudio diff --git a/lasp/device/lasp_daqconfig.py b/lasp/device/lasp_daqconfig.py index e249a4d..26b9d09 100644 --- a/lasp/device/lasp_daqconfig.py +++ b/lasp/device/lasp_daqconfig.py @@ -10,12 +10,30 @@ Data Acquistiion (DAQ) device descriptors, and the DAQ devices themselves """ from dataclasses import dataclass, field +@dataclass +class DeviceInfo: + index: int + probed: bool + name: str + outputchannels: int + inputchannels: int + duplexchannels: int + samplerates: list + sampleformats: list + prefsamplerate: int + + +from ..lasp_common import lasp_shelve + @dataclass class DAQInputChannel: channel_enabled: bool channel_name: str sensitivity: float + + + @dataclass class DAQConfiguration: """ @@ -24,6 +42,8 @@ class DAQConfiguration: Args: input_device_name: ASCII name with which to open the device when connected outut_device_name: ASCII name with which to open the device when connected + + ============================== en_format: index of the format in the list of sample formats en_input_rate: index of enabled input sampling frequency [Hz] in the list of frequencies. @@ -38,61 +58,43 @@ class DAQConfiguration: en_output_rate: index in the list of possible output sampling frequencies. en_output_channels: list of enabled output channels + =============================== """ - input_device_name: bytes - output_device_name: bytes - en_bit_depth: int + duplex_mode: bool + + input_device_name: str + output_device_name: str + + en_input_sample_format: str + en_output_sample_format: str en_input_rate: int + en_output_rate: int + en_input_channels: list - en_input_gain_settings: list = field(default_factory=list) - en_output_rate: int = -1 - - def match(self, device): - """ - Returns True when a device specification dictionary matches to the - configuration. - - Args: - device: dictionary specifying device settings - """ - match = True - if self.cardlongnamematch is not None: - match &= self.cardlongnamematch in device.cardlongname - if self.cardname is not None: - match &= self.cardname == device.cardname - if self.device_name is not None: - match &= self.device_name == device.device_name - match &= self.en_format < len(device.available_formats) - match &= self.en_input_rate < len(device.available_input_rates) - match &= max( - self.en_input_channels) < device.max_input_channels - if len(self.en_output_channels) > 0: - match &= max( - self.en_output_channels) < device.max_output_channels - match &= self.en_output_rate < len( - device.available_output_rates) - - return match @staticmethod - def emptyFromDeviceAndSettings(device): - return DAQConfiguration( - name = 'UNKNOWN' - input_device_name = - + def loadConfigs(): + """ + Returns a list of currently available configurations + """ + with lasp_shelve() as sh: + return sh['daqconfigs'] if 'daqconfigs' in sh.keys() else {} -def findDAQDevice(config: DAQConfiguration) -> DeviceInfo: - """ - Search for a DaQ device for the given configuration. + def saveConfig(self, name): + with lasp_shelve() as sh: + if 'daqconfigs' in sh.keys(): + cur_configs = sh['daqconfigs'] + else: + cur_configs = {} + cur_configs[name] = self + sh['daqconfigs'] = cur_configs - Args: - config: configuration to search a device for - """ - devices = query_devices() + @staticmethod + def deleteConfig(name): + with lasp_shelve() as sh: + cur_configs = sh['daqconfigs'] + del cur_configs[name] + sh['daqconfigs'] = cur_configs - for device in devices: - if config.match(device): - return device - return None diff --git a/lasp/device/lasp_rtaudio.pyx b/lasp/device/lasp_rtaudio.pyx index 30b9377..5d23512 100644 --- a/lasp/device/lasp_rtaudio.pyx +++ b/lasp/device/lasp_rtaudio.pyx @@ -1,6 +1,7 @@ import sys include "config.pxi" cimport cython +from .lasp_daqconfig import DeviceInfo from libcpp.string cimport string from libcpp.vector cimport vector from libc.string cimport memcpy @@ -99,57 +100,27 @@ cdef extern from "RtAudio.h" nogil: unsigned int getStreamSampleRate() void showWarnings(bool value) -cdef class SampleFormat: - cdef: - RtAudioFormat _format - string _name - public: - # The size in bytes of a single audio sample, for the current - # format - unsigned int sampleSize - - def __cinit__(self, RtAudioFormat format_): - self._format = format_ - if format_ == RTAUDIO_SINT8: - self._name = b'8-bit integers' - self.sampleSize = 1 - elif format_ == RTAUDIO_SINT16: - self._name = b'16-bit integers' - self.sampleSize = 2 - elif format_ == RTAUDIO_SINT24: - self._name = b'24-bit integers' - self.sampleSize = 3 - elif format_ == RTAUDIO_SINT32: - self._name = b'32-bit integers' - self.sampleSize = 4 - elif format_ == RTAUDIO_FLOAT32: - self._name = b'32-bit floats' - self.sampleSize = 4 - elif format_ == RTAUDIO_FLOAT64: - self._name = b'64-bit floats' - self.sampleSize = 8 - else: - raise ValueError('Invalid RtAudioFormat code') - - def __repr__(self): - return self._name.decode('utf-8') - - - def __str__(self): - return self.__repr__() - -# Pre-define format and expose to Python -Format_SINT8 = SampleFormat(RTAUDIO_SINT8) -Format_SINT16 = SampleFormat(RTAUDIO_SINT16) -Format_SINT24 = SampleFormat(RTAUDIO_SINT24) -Format_SINT32 = SampleFormat(RTAUDIO_SINT32) -Format_FLOAT32 = SampleFormat(RTAUDIO_FLOAT32) -Format_FLOAT64 = SampleFormat(RTAUDIO_FLOAT64) - +_formats_strkey = { + '8-bit integers': (RTAUDIO_SINT8, 1), + '16-bit integers': (RTAUDIO_SINT16,2), + '24-bit integers': (RTAUDIO_SINT24,3), + '32-bit integers': (RTAUDIO_SINT32,4), + '32-bit floats': (RTAUDIO_FLOAT32, 4), + '64-bit floats': (RTAUDIO_FLOAT64, 8), +} +_formats_rtkey = { + RTAUDIO_SINT8: ('8-bit integers', 1), + RTAUDIO_SINT16: ('16-bit integers',2), + RTAUDIO_SINT24: ('24-bit integers',3), + RTAUDIO_SINT32: ('32-bit integers',4), + RTAUDIO_FLOAT32: ('32-bit floats', 4), + RTAUDIO_FLOAT64: ('64-bit floats', 8), +} cdef class _Stream: cdef: object pyCallback - RtAudioFormat format + unsigned int sampleSize + RtAudioFormat sampleformat cppRtAudio.StreamParameters inputParams cppRtAudio.StreamParameters outputParams # These boolean values tell us whether the structs above here are @@ -226,7 +197,7 @@ cdef int audioCallback(void* outputBuffer, assert outputBuffer != NULL, "Bug: RtAudio does not give output buffer!" assert npy_output.shape[0] == stream.outputParams.nChannels, "Bug: channel mismatch in output buffer!" assert npy_output.shape[1] == nFrames, "Bug: frame mismatch in output buffer!" - assert npy_output.itemsize == stream._format.sampleSize, "Bug: invalid sample type in output buffer!" + assert npy_output.itemsize == stream.sampleSize, "Bug: invalid sample type in output buffer!" except AssertionError as e: print(e) fromNPYToBuffer(npy_output, outputBuffer) @@ -236,6 +207,8 @@ cdef void errorCallback(RtAudioError.Type _type,const string& errortxt) nogil: pass + + cdef class RtAudio: cdef: cppRtAudio _rtaudio @@ -263,31 +236,26 @@ cdef class RtAudio: Return device information of the current device """ cdef cppRtAudio.DeviceInfo devinfo = self._rtaudio.getDeviceInfo(device) - pydevinfo = {} - pydevinfo['index'] = device - pydevinfo['probed'] = devinfo.probed - pydevinfo['name'] = devinfo.name.decode('utf-8') - pydevinfo['outputchannels'] = devinfo.outputChannels - pydevinfo['inputchannels'] = devinfo.inputChannels - pydevinfo['duplexchannels'] = devinfo.duplexChannels - pydevinfo['isdefaultoutput'] = devinfo.isDefaultOutput - pydevinfo['isdefaultinpput'] = devinfo.isDefaultInput - pydevinfo['samplerates'] = devinfo.sampleRates - pydevinfo['prefsamprate'] = devinfo.preferredSampleRate - # Manually add these - nf = devinfo.nativeFormats sampleformats = [] + nf = devinfo.nativeFormats for format_ in [ RTAUDIO_SINT8, RTAUDIO_SINT16, RTAUDIO_SINT24, RTAUDIO_SINT32, RTAUDIO_FLOAT32, RTAUDIO_FLOAT64]: if nf & format_: - sampleformats.append(SampleFormat(format_)) - - pydevinfo['sampleformats'] = sampleformats - return pydevinfo + sampleformats.append(_formats_rtkey[format_][0]) + return DeviceInfo( + index = device, + probed = devinfo.probed, + name = devinfo.name.decode('utf-8'), + outputchannels = devinfo.outputChannels, + inputchannels = devinfo.inputChannels, + duplexchannels = devinfo.duplexChannels, + samplerates = devinfo.sampleRates, + sampleformats = sampleformats, + prefsamplerate = devinfo.preferredSampleRate) def openStream(self,object outputParams, object inputParams, - SampleFormat format, + str sampleformat, unsigned int sampleRate, unsigned int bufferFrames, object pyCallback, @@ -342,7 +310,7 @@ cdef class RtAudio: self._stream.bufferFrames = bufferFrames self._rtaudio.openStream(rtOutputParams_ptr, rtInputParams_ptr, - format._format, + _formats_strkey[sampleformat][0], sampleRate, &self._stream.bufferFrames, audioCallback, diff --git a/lasp/lasp_avstream.py b/lasp/lasp_avstream.py index 67d5feb..4f9f3a5 100644 --- a/lasp/lasp_avstream.py +++ b/lasp/lasp_avstream.py @@ -7,7 +7,7 @@ import cv2 as cv from .lasp_atomic import Atomic from threading import Thread, Condition, Lock import time -from .device import DAQDevice, roga_plugndaq +from .device import DAQConfiguration import numpy as np __all__ = ['AvType', 'AvStream'] @@ -21,9 +21,16 @@ class AvType: class AvStream: - def __init__(self, daqconfig=roga_plugndaq, video=None): + def __init__(self, + rtaudio_input, + rtaudio_output, + input_device, + output_device, daqconfig, video=None): + self.daqconfig = daqconfig + self.input_device = input_device + self.output_device = output_device try: daq = DAQDevice(daqconfig) self.nchannels = len(daq.channels_en) diff --git a/lasp/lasp_common.py b/lasp/lasp_common.py index 9687943..a850a34 100644 --- a/lasp/lasp_common.py +++ b/lasp/lasp_common.py @@ -8,7 +8,7 @@ Common definitions used throughout the code. """ __all__ = ['P_REF', 'FreqWeighting', 'TimeWeighting', 'getTime', - 'getFreq', 'lasp_shelve' + 'getFreq', 'lasp_shelve', 'W_REF', 'U_REF', 'I_REF'] lasp_appdir = appdirs.user_data_dir('Lasp', 'ASCEE') @@ -23,12 +23,21 @@ if not os.path.exists(lasp_appdir): class lasp_shelve: + refcount = 0 + shelve = None + def __enter__(self): - self.shelve = shelve.open(os.path.join(lasp_appdir, 'config.shelve')) - return self.shelve + if lasp_shelve.shelve is None: + assert lasp_shelve.refcount == 0 + lasp_shelve.shelve = shelve.open(os.path.join(lasp_appdir, 'config.shelve')) + lasp_shelve.refcount += 1 + return lasp_shelve.shelve def __exit__(self, type, value, traceback): - self.shelve.close() + lasp_shelve.refcount -= 1 + if lasp_shelve.refcount == 0: + lasp_shelve.shelve.close() + lasp_shelve.shelve = None