DaqConfiguration and device info will have a direct Cython wrapper

This commit is contained in:
Anne de Jong 2020-10-07 21:10:19 +02:00
parent a3963c4595
commit a43857070b
12 changed files with 280 additions and 291 deletions

View File

@ -4,12 +4,19 @@ include_directories(/usr/include/rtaudio)
add_library(cpp_daq lasp_cppdaq.cpp lasp_cppuldaq.cpp )
set_source_files_properties(lasp_daq.pyx PROPERTIES CYTHON_IS_CXX TRUE)
set_source_files_properties(lasp_deviceinfo.pyx PROPERTIES CYTHON_IS_CXX TRUE)
set_source_files_properties(lasp_daq.cxx PROPERTIES COMPILE_FLAGS
"${CMAKE_CXX_FLAGS} ${CYTHON_EXTRA_CXX_FLAGS}")
set_source_files_properties(lasp_deviceinfo.cxx PROPERTIES COMPILE_FLAGS
"${CMAKE_CXX_FLAGS} ${CYTHON_EXTRA_CXX_FLAGS}")
cython_add_module(lasp_daq lasp_daq.pyx)
cython_add_module(lasp_deviceinfo lasp_deviceinfo.pyx)
target_link_libraries(lasp_daq cpp_daq uldaq rtaudio pthread)
target_link_libraries(lasp_deviceinfo cpp_daq uldaq rtaudio pthread)
if(win32)
target_link_libraries(lasp_daq python37)
endif(win32)

View File

@ -2,4 +2,5 @@
from .lasp_daqconfig import *
from .lasp_avtype import *
from .lasp_daq import *
from .lasp_deviceinfo import *

View File

@ -1,7 +1,6 @@
import sys
include "config.pxi"
cimport cython
from .lasp_daqconfig import DeviceInfo
from .lasp_avtype import AvType
from libcpp.string cimport string
from libcpp.vector cimport vector
@ -10,10 +9,6 @@ from libc.stdio cimport printf, fprintf, stderr
from libc.string cimport memcpy, memset
from cpython.ref cimport PyObject,Py_INCREF, Py_DECREF
__all__ = ['RtAudio', 'get_numpy_dtype_from_format_string',
'get_sampwidth_from_format_string']
cdef extern from "lasp_cppthread.h" nogil:
cdef cppclass CPPThread[T,F]:
CPPThread(F threadfunction, T data)
@ -30,7 +25,6 @@ cdef extern from "lasp_cppqueue.h" nogil:
size_t size() const
bool empty() const
cdef extern from "atomic" namespace "std" nogil:
cdef cppclass atomic[T]:
T load()
@ -43,13 +37,60 @@ cdef extern from "lasp_pyarray.h":
bool transfer_ownership,
bool F_contiguous)
ctypedef unsigned us
ctypedef vector[bool] boolvec
cdef inline void copyChannel(void* to, void* from_,
unsigned bytesperchan,
unsigned toindex,
unsigned fromindex) nogil:
memcpy(<void*> &((<char*> to)[bytesperchan*toindex]),
<void*> &((<char*> from_)[bytesperchan*fromindex]),
bytesperchan)
cdef extern from "lasp_cppdaq.h" nogil:
cdef cppclass cppDaq "Daq":
void start(SafeQueue[void*] *inQueue,
SafeQueue[void*] *outQueue) except +
void stop()
double samplerate()
us neninchannels()
us nenoutchannels()
DataType getDataType()
cdef cppclass DaqApi:
string apiname
unsigned apicode
unsigned api_specific_subcode
cdef cppclass DataType:
string name
unsigned sw
bool is_floating
DataType dtype_fl64
cdef cppclass cppDeviceInfo "DeviceInfo":
DaqApi api
string name
unsigned devindex
vector[DataType] availableDataTypes
vector[double] availableSampleRates
int prefSampleRateIndex
unsigned ninchannels
unsigned noutchannels
bool hasInputIEPE
bool hasInputACCouplingSwitch
bool hasInputTrigger
vector[double] inputRanges
cdef cppclass DaqConfiguration:
boolvec eninchannels
boolvec enoutchannels
unsigned sampleRateIndex
DataType datatype
bool monitorOutput
unsigned nFramesPerBlock;
boolvec inputIEPEEnabled;
boolvec inputACCouplingMode;
boolvec inputHighRange;
cdef cppclass DaqDevices:
@staticmethod
cppDaq* createDaqDevice(cppDeviceInfo&, DaqConfiguration&)
@staticmethod
vector[cppDeviceInfo] getDeviceInfo()

View File

@ -28,9 +28,13 @@ class DataType {
DataType(const char *name, unsigned sw, bool is_floating)
: name(name), sw(sw), is_floating(is_floating) {}
DataType():
name("invalid data type"),
sw(0),
is_floating(false) {}
};
const DataType dtype_invalid("invalid data type", 0, false);
const DataType dtype_invalid;
const DataType dtype_fl64("64-bits floating point", 4, true);
const DataType dtype_int8("8-bits integers", 1, false);
const DataType dtype_int16("16-bits integers", 2, false);
@ -100,6 +104,10 @@ class DeviceInfo {
bool hasInputIEPE = false;
bool hasInputACCouplingSwitch = false;
bool hasInputTrigger = false;
vector<double> inputRanges;
/* DeviceInfo(): */
/* datatype(dtype_invalid) { } */
double prefSampleRate() const {
if ((prefSampleRateIndex < availableSampleRates.size()) &&

View File

@ -1,66 +1,10 @@
cimport cython
from cpython.ref cimport PyObject,Py_INCREF, Py_DECREF
from .lasp_daqconfig import (DeviceInfo, InputMode, Range as pyRange,
from .lasp_daqconfig import (Range as pyRange,
DAQChannel)
from .lasp_avtype import AvType
__all__ = ['UlDaq']
DEF MAX_DEF_COUNT = 100
DEF UL_ERR_MSG_LEN = 512
ctypedef unsigned us
ctypedef vector[bool] boolvec
cdef extern from "lasp_cppdaq.h" nogil:
cdef cppclass cppDaq "Daq":
void start(SafeQueue[void*] *inQueue,
SafeQueue[void*] *outQueue) except +
void stop()
double samplerate()
us neninchannels()
us nenoutchannels()
DataType getDataType()
cdef cppclass DaqApi:
string apiname
unsigned apicode
unsigned api_specific_subcode
cdef cppclass DataType:
string name
unsigned sw
bool is_floating
DataType dtype_fl64
cdef cppclass cppDeviceInfo "DeviceInfo":
DaqApi api
string name
unsigned devindex
vector[DataType] availableDataTypes
vector[double] availableSampleRates
int prefSampleRateIndex
unsigned ninchannels
unsigned noutchannels
bool hasInputIEPE
bool hasInputACCouplingSwitch
bool hasInputTrigger
cdef cppclass DaqConfiguration:
boolvec eninchannels
boolvec enoutchannels
unsigned sampleRateIndex
DataType datatype
bool monitorOutput
unsigned nFramesPerBlock;
boolvec inputIEPEEnabled;
boolvec inputACCouplingMode;
boolvec inputHighRange;
cdef cppclass DaqDevices:
@staticmethod
cppDaq* createDaqDevice(cppDeviceInfo&, DaqConfiguration&)
__all__ = ['Daq']
ctypedef struct PyStreamData:
@ -80,7 +24,7 @@ ctypedef struct PyStreamData:
double samplerate
# If these queue pointers are NULL, it means the stream does not have an
# If either of these queue pointers are NULL, it means the stream does not have an
# input, or output.
SafeQueue[void*] *inQueue
SafeQueue[void*] *outQueue
@ -196,139 +140,135 @@ cdef class Daq:
def isRunning(self):
return self.sd is not NULL
@cython.nonecheck(True)
def start(self, avstream):
"""
Opens a stream with specified parameters
# @cython.nonecheck(True)
# def start(self, avstream):
# """
# Opens a stream with specified parameters
Args:
avstream: AvStream instance
# Args:
# avstream: AvStream instance
Returns: None
"""
# Returns: None
# """
if self.sd is not NULL:
assert self.daq_device is not NULL
raise RuntimeError('Stream is already opened.')
# if self.sd is not NULL:
# assert self.daq_device is not NULL
# raise RuntimeError('Stream is already opened.')
daqconfig = avstream.daqconfig
avtype = avstream.avtype
device = avstream.device
# daqconfig = avstream.daqconfig
# avtype = avstream.avtype
# device = avstream.device
cdef:
bint duplex_mode = daqconfig.duplex_mode
bint monitorOutput = daqconfig.monitor_gen
# cdef:
# bint duplex_mode = daqconfig.duplex_mode
# bint monitorOutput = daqconfig.monitor_gen
unsigned int nFramesPerBlock = daqconfig.nFramesPerBlock
unsigned int samplerate
# unsigned int nFramesPerBlock = daqconfig.nFramesPerBlock
# unsigned int samplerate
int i
bint in_stream=False
bint out_stream=False
# int i
# bint in_stream=False
# bint out_stream=False
cppDaq* daq_device
# cppDeviceInfo devinfo
# DaqConfiguration cppconfig
if daqconfig.nFramesPerBlock > 8192 or daqconfig.nFramesPerBlock < 512:
raise ValueError('Invalid number of nFramesPerBlock')
# cppDaq* daq_device
if daqconfig.outputDelayBlocks != 0:
print('WARNING: OutputDelayBlocks not supported by API')
# if daqconfig.nFramesPerBlock > 8192 or daqconfig.nFramesPerBlock < 512:
# raise ValueError('Invalid number of nFramesPerBlock')
# Determine sample rate and sample format, determine whether we are an
# in or an output stream, or both
print(f'AvType: {avtype}')
print(f'Dup: {duplex_mode}')
if avtype == AvType.audio_input or duplex_mode:
# Here, we override the sample format in case of duplex mode.
sampleformat = daqconfig.en_input_sample_format
samplerate = int(daqconfig.en_input_rate)
in_stream = True
if duplex_mode:
fprintf(stderr, 'Duplex mode enabled\n')
out_stream = True
elif avtype == AvType.audio_output:
sampleformat = daqconfig.en_output_sample_format
samplerate = int(daqconfig.en_output_rate)
out_stream = True
else:
raise ValueError(f'Invalid stream type {avtype}')
if out_stream and daqconfig.firstEnabledOutputChannelNumber() == -1:
raise RuntimeError('No output channels enabled')
# # if daqconfig.outputDelayBlocks != 0:
# # print('WARNING: OutputDelayBlocks not supported by API')
if in_stream and daqconfig.firstEnabledInputChannelNumber() == -1:
raise RuntimeError('No input channels enabled')
# # Determine sample rate and sample format, determine whether we are an
# # in or an output stream, or both
# # print(f'AvType: {avtype}')
# # print(f'Dup: {duplex_mode}')
# if avtype == AvType.audio_input or duplex_mode:
# # Here, we override the sample format in case of duplex mode.
# sampleformat = daqconfig.en_input_sample_format
# samplerate = int(daqconfig.en_input_rate)
# in_stream = True
# if duplex_mode:
# fprintf(stderr, 'Duplex mode enabled\n')
# out_stream = True
# elif avtype == AvType.audio_output:
# sampleformat = daqconfig.en_output_sample_format
# samplerate = int(daqconfig.en_output_rate)
# out_stream = True
# else:
# raise ValueError(f'Invalid stream type {avtype}')
# if out_stream and daqconfig.firstEnabledOutputChannelNumber() == -1:
# raise RuntimeError('No output channels enabled')
# if in_stream and daqconfig.firstEnabledInputChannelNumber() == -1:
# raise RuntimeError('No input channels enabled')
# All set, allocate the stream!
self.sd = <PyStreamData*> malloc(sizeof(PyStreamData))
if self.sd == NULL:
raise MemoryError('Could not allocate stream: memory error.')
# # All set, allocate the stream!
# self.sd = <PyStreamData*> malloc(sizeof(PyStreamData))
# if self.sd == NULL:
# raise MemoryError('Could not allocate stream: memory error.')
self.sd.stopThread.store(False)
self.sd.inQueue = NULL
self.sd.outQueue = NULL
# self.sd.stopThread.store(False)
# self.sd.inQueue = NULL
# self.sd.outQueue = NULL
self.sd.thread = NULL
self.sd.samplerate = <double> samplerate
# self.sd.thread = NULL
# self.sd.samplerate = <double> samplerate
self.sd.ninchannels = 0
self.sd.noutchannels = 0
self.sd.nBytesPerChan = daqconfig.nFramesPerBlock*sizeof(double)
self.sd.nFramesPerBlock = daqconfig.nFramesPerBlock
# self.sd.ninchannels = 0
# self.sd.noutchannels = 0
# self.sd.nBytesPerChan = daqconfig.nFramesPerBlock*sizeof(double)
# self.sd.nFramesPerBlock = daqconfig.nFramesPerBlock
# Create channel maps for in channels, set in stream
# parameters
inch_enabled = 4*[False]
if in_stream:
inch_enabled = [True if ch.channel_enabled else False for ch in
daqconfig.getInputChannels()]
# # Create channel maps for in channels, set in stream
# # parameters
# inch_enabled = 4*[False]
# if in_stream:
# inch_enabled = [True if ch.channel_enabled else False for ch in
# daqconfig.getInputChannels()]
self.sd.inQueue = new SafeQueue[void*]()
# self.sd.inQueue = new SafeQueue[void*]()
# Create channel maps for output channels
outch_enabled = 1*[False]
if out_stream:
print('Stream is output stream')
outch_enabled = [True if ch.channel_enabled else False for ch in
daqconfig.getOutputChannels()]
# # Create channel maps for output channels
# outch_enabled = 1*[False]
# if out_stream:
# outch_enabled = [True if ch.channel_enabled else False for ch in
# daqconfig.getOutputChannels()]
self.sd.outQueue = new SafeQueue[void*]()
# self.sd.outQueue = new SafeQueue[void*]()
# if 'DT9837A' in device.name:
# daq_device = new DT9837A(
# daqconfig.nFramesPerBlock,
# inch_enabled,
# outch_enabled,
# samplerate,
# monitorOutput,
# device.index)
# else:
# raise RuntimeError(f'Device {device.name} not found or not configured')
# daq_device = createDaqDevice(devinfo, cppconfig)
self.sd.pyCallback = <PyObject*> avstream._audioCallback
self.sd.ninchannels = daq_device.neninchannels()
self.sd.noutchannels = daq_device.nenoutchannels()
# self.sd.pyCallback = <PyObject*> avstream._audioCallback
# self.sd.ninchannels = daq_device.neninchannels()
# self.sd.noutchannels = daq_device.nenoutchannels()
self.daq_device = daq_device
# self.daq_device = daq_device
# Increase reference count to the callback
Py_INCREF(<object> avstream._audioCallback)
# # Increase reference count to the callback
# Py_INCREF(<object> avstream._audioCallback)
with nogil:
self.sd.thread = new CPPThread[void*, void (*)(void*)](audioCallbackPythonThreadFunction,
<void*> self.sd)
# with nogil:
# self.sd.thread = new CPPThread[void*, void (*)(void*)](audioCallbackPythonThreadFunction,
# <void*> self.sd)
# Allow it to start
CPPsleep_ms(500)
# # Allow it to start
# CPPsleep_ms(500)
self.daq_device.start(
self.sd.inQueue,
self.sd.outQueue)
# self.daq_device.start(
# self.sd.inQueue,
# self.sd.outQueue)
return nFramesPerBlock, self.daq_device.samplerate()
# return nFramesPerBlock, self.daq_device.samplerate()
def stop(self):
if self.sd is NULL:
@ -341,7 +281,6 @@ cdef class Daq:
self.cleanupStream(self.sd)
self.sd = NULL
cdef cleanupStream(self, PyStreamData* sd):
with nogil:
@ -375,62 +314,3 @@ cdef class Daq:
free(sd)
def getDeviceInfo(self):
"""
Returns device information objects (DeviceInfo) for all available
devices
"""
cdef:
pass
# DaqDeviceDescriptor devdescriptors[MAX_DEF_COUNT]
# DaqDeviceDescriptor descriptor
# DaqDeviceInterface interfaceType = ANY_IFC
# DaqDeviceHandle handle
# UlError err
# unsigned int numdevs = MAX_DEF_COUNT
# unsigned deviceno
# if self.sd is not NULL:
# assert self.daq_device is not NULL
# raise RuntimeError('Cannot acquire device info: stream is already opened.')
# err = ulGetDaqDeviceInventory(interfaceType,
# devdescriptors,
# &numdevs)
# if(err != ERR_NO_ERROR):
# raise RuntimeError(f'Device inventarization failed: {err}')
# py_devinfo = []
# for deviceno in range(numdevs):
# descriptor = devdescriptors[deviceno]
# if descriptor.productName == b'DT9837A':
# # Create proper interface name
# if descriptor.devInterface == DaqDeviceInterface.USB_IFC:
# name = 'USB - '
# elif descriptor.devInterface == DaqDeviceInterface.BLUETOOTH_IFC:
# name = 'Bluetooth - '
# elif descriptor.devInterface == DaqDeviceInterface.ETHERNET_IFC:
# name = 'Ethernet - '
# name += descriptor.productName.decode('utf-8') + ', id ' + \
# descriptor.uniqueId.decode('utf-8')
# d = DeviceInfo(
# api = -1,
# index = deviceno,
# probed = True,
# name = name,
# outputchannels = 1,
# inputchannels = 4,
# duplexchannels = 0,
# samplerates = [10000, 16000, 20000, 32000, 48000, 50000] ,
# sampleformats = ['64-bit floats'],
# prefsamplerate = 48000,
# hasInputIEPE = True)
# py_devinfo.append(d)
# return py_devinfo

View File

@ -12,34 +12,13 @@ from dataclasses import dataclass, field
from typing import List
from dataclasses_json import dataclass_json
from ..lasp_common import Qty, SIQtys, lasp_shelve
@dataclass
class DAQApi:
backendname: str
apiname: str
internal_nr: int
def description(self):
return self.backendname + ' - ' + self.apiname
class DAQApis:
apis = []
@staticmethod
def addApi(api):
DAQApis.apis.append(api)
class InputMode:
differential = 'Differential'
single_ended = 'Single-ended'
pseudo_differential = 'Pseudo-differential'
undefined = 'Undefined'
# class InputMode:
# differential = 'Differential'
# single_ended = 'Single-ended'
# pseudo_differential = 'Pseudo-differential'
# undefined = 'Undefined'
class CouplingMode:
ac = 'AC'
@ -53,21 +32,6 @@ class Range:
undefined = 'Undefined'
@dataclass
class DeviceInfo:
api: int
index: int
probed: bool #
name: str
outputchannels: int
inputchannels: int
duplexchannels: int
samplerates: list
sampleformats: list
prefsamplerate: int
hasInputIEPE: bool = False
@dataclass_json
@dataclass
class DAQChannel:
@ -79,7 +43,6 @@ class DAQChannel:
IEPE_enabled: bool = False
@dataclass_json
@dataclass
class DAQConfiguration:
@ -130,7 +93,7 @@ class DAQConfiguration:
monitor_gen: bool = False
outputDelayBlocks: int = 0
nFramesPerBlock: int = 512
nFramesPerBlock: int = 1024
def getInputChannels(self):
return self.input_channel_configs

View File

@ -0,0 +1,5 @@
include "lasp_common_decls.pxd"
cdef class DeviceInfo:
cdef:
cppDeviceInfo devinfo

View File

@ -0,0 +1,74 @@
__all__ = ['DeviceInfo']
cdef class DeviceInfo:
def __cinit__(self):
pass
@property
def api(self): return self.devinfo.api.apiname.decode('utf-8')
@property
def name(self): return self.devinfo.name.decode('utf-8')
@property
def devindex(self): return self.devinfo.devindex
@property
def ninchannels(self): return self.devinfo.ninchannels
@property
def noutchannels(self): return self.devinfo.noutchannels
@property
def availableSampleRates(self): return self.devinfo.availableSampleRates
@property
def availableDataTypes(self):
pydtypes = []
for datatype in self.devinfo.availableDataTypes:
pydtypes.append(datatype.name.decode('utf-8'))
return pydtypes
@property
def prefSampleRateIndex(self):
return self.devinfo.prefSampleRateIndex
@property
def hasInputIEPE(self):
return self.devinfo.hasInputIEPE
@property
def hasInputACCouplingSwitch(self):
return self.devinfo.hasInputACCouplingSwitch
@property
def hasInputTrigger(self):
return self.devinfo.hasInputTrigger
@property
def inputRanges(self):
return self.devinfo.inputRanges
@staticmethod
def getDeviceInfo():
"""
Returns device information objects (DeviceInfo) for all available
devices
"""
cdef:
vector[cppDeviceInfo] devinfos
us numdevs, devno
cppDeviceInfo* devinfo
devinfos = DaqDevices.getDeviceInfo()
numdevs = devinfos.size()
pydevinfos = []
for devno in range(numdevs):
devinfo = &(devinfos[devno])
d = DeviceInfo()
d.devinfo = devinfo[0]
pydevinfos.append(d)
return pydevinfos

View File

@ -3,7 +3,6 @@ include "lasp_common_decls.pxd"
__all__ = ['RtAudio', 'get_numpy_dtype_from_format_string',
'get_sampwidth_from_format_string']
cdef extern from "RtAudio.h" nogil:
ctypedef unsigned long RtAudioStreamStatus
RtAudioStreamStatus RTAUDIO_INPUT_OVERFLOW
@ -442,9 +441,12 @@ cdef class RtAudio:
PyStreamData* sd
int api
def __cinit__(self, unsigned int iapi):
def __cinit__(self, pyapi):
if pyapi.apiname != 'RtAudio':
raise RuntimeError('RtAudio constructor called with invalid Api instance')
cdef:
cppRtAudio.Api api = <cppRtAudio.Api> iapi
cppRtAudio.Api api = <cppRtAudio.Api> pyapi.internalnr
self._rtaudio = new cppRtAudio(api)
self.sd = NULL
self._rtaudio.showWarnings(True)
@ -453,22 +455,29 @@ cdef class RtAudio:
def __dealloc__(self):
if self.sd is not NULL:
# fprintf(stderr, 'Force closing stream...')
if self._rtaudio.isStreamRunning():
self._rtaudio.stopStream()
self._rtaudio.closeStream()
self.cleanupStream(self.sd)
self.sd = NULL
del self._rtaudio
@staticmethod
def getApi():
def getApis():
cdef:
vector[cppRtAudio.Api] apis
cppRtAudio.getCompiledApi(apis)
apidict = {}
apilist = []
for api in apis:
apidict[<int> api] = {
'displayname': 'RtAudio - ' + cppRtAudio.getApiDisplayName(api).decode('utf-8'),
'name': cppRtAudio.getApiName(api).decode('utf-8')
}
return apidict
apilist.append(
DAQApi(
backendname= 'RtAudio',
apiname = cppRtAudio.getApiName(api).decode('utf-8'),
internalnr=<int> api))
return apilist
cpdef unsigned int getDefaultOutputDevice(self):
return self._rtaudio.getDefaultOutputDevice()

View File

@ -9,10 +9,11 @@ from threading import Thread, Condition, Lock
import numpy as np
import time
from .device import (RtAudio, DeviceInfo, DAQConfiguration,
get_numpy_dtype_from_format_string,
get_sampwidth_from_format_string, AvType,
UlDaq)
from .device import (Daq, DeviceInfo, DAQConfiguration,
# get_numpy_dtype_from_format_string,
# get_sampwidth_from_format_string,
AvType,
)
__all__ = ['AvStream']

0
lasp_daqconfig.pxd Normal file
View File

0
lasp_daqconfig.pyx Normal file
View File