RtAudio in a theoretically working state. Now its time for some testing.

This commit is contained in:
Anne de Jong 2020-10-17 19:14:24 +02:00
parent 622dd1f319
commit 6b8abb878a
11 changed files with 680 additions and 338 deletions

View File

@ -27,4 +27,4 @@ add_library(lasp_lib
target_link_libraries(lasp_lib ${LASP_FFT_LIBRARY} openblas)
target_link_libraries(lasp_lib ${LASP_FFT_LIBRARY} openblas pthread)

View File

@ -1,21 +1,21 @@
set(cpp_daq_files lasp_cppdaq.cpp)
set(mycpp_daq_linklibs pthread)
set(cpp_daq_linklibs pthread)
if(LASP_RTAUDIO)
include_directories(/usr/include/rtaudio)
list(APPEND cpp_daq_files lasp_cpprtaudio.cpp)
list(PREPEND mycpp_daq_linklibs rtaudio)
list(PREPEND cpp_daq_linklibs rtaudio)
endif()
if(LASP_ULDAQ)
list(APPEND cpp_daq_files lasp_cppuldaq.cpp)
list(PREPEND mycpp_daq_linklibs uldaq)
list(PREPEND cpp_daq_linklibs uldaq)
endif()
if(win32)
list(APPEND mycpp_daq_linklibs python)
list(APPEND cpp_daq_linklibs python)
endif(win32)
add_library(cpp_daq ${cpp_daq_files})
target_link_libraries(cpp_daq ${mycpp_daq_linklibs})
target_link_libraries(cpp_daq ${cpp_daq_linklibs})
foreach(cython_file lasp_daq lasp_deviceinfo lasp_daqconfig)
@ -27,7 +27,7 @@ foreach(cython_file lasp_daq lasp_deviceinfo lasp_daqconfig)
cython_add_module(${cython_file} ${cython_file}.pyx)
target_link_libraries(${cython_file} ${mycpp_daq_linklibs})
target_link_libraries(${cython_file} cpp_daq ${cpp_daq_linklibs})
endforeach()
@ -36,4 +36,4 @@ endforeach()
# also used in the testing directory. But better to already link cpp_daq with
# linklibs.
# set(cpp_daq_linklibs "${mycpp_daq_linklibs}" CACHE INTERNAL "cpp_daq_linklibs")
# set(cpp_daq_linklibs "${cpp_daq_linklibs}" CACHE INTERNAL "cpp_daq_linklibs")

View File

@ -49,55 +49,96 @@ cdef extern from "lasp_cppdaq.h" nogil:
unsigned apicode
unsigned api_specific_subcode
@staticmethod
vector[DaqApi] getAvailableApis();
cdef cppclass DataType:
string name
unsigned sw
bool is_floating
DataType dtype_fl64
bool operator==(const DataType&)
DataType dtype_fl64
DataType dtype_fl32
DataType dtype_int8
DataType dtype_int16
DataType dtype_int32
DataType dtype_invalid
cdef cppclass cppDeviceInfo "DeviceInfo":
DaqApi api
string device_name
unsigned devindex
vector[DataType] availableDataTypes
vector[double] availableSampleRates
vector[us] availableFramesPerBlock
int api_specific_devindex
vector[DataType] availableDataTypes
int prefDataTypeIndex
vector[double] availableSampleRates
int prefSampleRateIndex
vector[us] availableFramesPerBlock
unsigned prefFramesPerBlockIndex
dvec availableInputRanges
int prefInputRangeIndex
int prefFramesPerBlockIndex
unsigned ninchannels
unsigned noutchannels
bool hasInputIEPE
bool hasInputACCouplingSwitch
bool hasInputTrigger
vector[double] availableInputRanges
cdef cppclass DaqConfiguration:
cdef cppclass cppDaqConfiguration "DaqConfiguration":
DaqApi api
string device_name
boolvec eninchannels
boolvec enoutchannels
vector[string] channel_names
vector[double] channel_sensitivities
unsigned sampleRateIndex
DataType datatype
bool monitorOutput
unsigned nFramesPerBlock;
boolvec inputIEPEEnabled;
boolvec inputACCouplingMode;
usvec inputRangeIndices;
vector[double] inchannel_sensitivities
vector[string] inchannel_names
vector[string] outchannel_names
us sampleRateIndex
us dataTypeIndex
us framesPerBlockIndex
bool monitorOutput
vector[us] input_qty_idx
boolvec inputIEPEEnabled
boolvec inputACCouplingMode
usvec inputRangeIndices
DaqConfiguration()
int getHighestInChannel()
int getHighestOutChannel()
int getLowestInChannel()
int getLowestOutChannel()
cdef cppclass cppDaq "Daq":
void start(SafeQueue[void*] *inQueue,
SafeQueue[void*] *outQueue) except +
void stop()
void stop() except +
double samplerate()
us neninchannels()
us neninchannels(bool include_monitorchannel=False)
us nenoutchannels()
DataType getDataType()
DataType dataType()
us framesPerBlock()
bool isRunning()
bool duplexMode()
@staticmethod
cppDaq* createDaqDevice(cppDeviceInfo&, DaqConfiguration&)
cppDaq* createDaq(cppDeviceInfo&, cppDaqConfiguration&) except +
@staticmethod
vector[cppDeviceInfo] getDeviceInfo()
vector[cppDeviceInfo] getDeviceInfo() except +

View File

@ -24,6 +24,24 @@ vector<DeviceInfo> Daq::getDeviceInfo() {
return devs;
}
vector<DaqApi> DaqApi::getAvailableApis() {
vector<DaqApi> apis;
apis.resize(6);
#ifdef HAS_ULDAQ_API
apis[uldaqapi.apicode] = uldaqapi;
#endif
#ifdef HAS_RTAUDIO_API
apis[rtaudioAlsaApi.apicode] = rtaudioAlsaApi;
apis[rtaudioPulseaudioApi.apicode] = rtaudioPulseaudioApi;
apis[rtaudioWasapiApi.apicode] = rtaudioWasapiApi;
apis[rtaudioDsApi.apicode] = rtaudioDsApi;
apis[rtaudioAsioApi.apicode] = rtaudioAsioApi;
#endif
return apis;
}
DaqConfiguration::DaqConfiguration(const DeviceInfo &device) {
api = device.api;
@ -61,19 +79,37 @@ bool DaqConfiguration::match(const DeviceInfo& dev) const {
return (dev.device_name == device_name && dev.api == api);
}
Daq *Daq::createDevice(const DaqConfiguration &config,
const vector<DeviceInfo> &devinfos) {
int DaqConfiguration::getHighestInChannel() const {
for(int i=eninchannels.size()-1; i>-1;i--) {
if(eninchannels[i]) return i;
}
return -1;
}
bool match;
DeviceInfo devinfo;
for (auto cur_devinfo : devinfos) {
if ((match = config.match(cur_devinfo))) {
devinfo = cur_devinfo;
break;
int DaqConfiguration::getHighestOutChannel() const {
for(us i=enoutchannels.size()-1; i>=0;i--) {
if(enoutchannels[i]) return i;
}
return -1;
}
int DaqConfiguration::getLowestInChannel() const {
for(us i=0; i<eninchannels.size();i++) {
if(eninchannels[i]) return i;
}
if (!match) {
return NULL;
return -1;
}
int DaqConfiguration::getLowestOutChannel() const {
for(us i=0; i<enoutchannels.size();i++) {
if(enoutchannels[i]) return i;
}
return -1;
}
Daq *Daq::createDaq(const DeviceInfo& devinfo,
const DaqConfiguration &config) {
if(!config.match(devinfo)) {
throw runtime_error("DaqConfiguration does not match device info");
}
// Some basic sanity checks
@ -112,13 +148,14 @@ Daq::Daq(const DeviceInfo &devinfo, const DaqConfiguration &config)
double Daq::samplerate() const {
mutexlock lock(mutex);
assert(sampleRateIndex < availableSampleRates.size());
return availableSampleRates[sampleRateIndex];
}
DataType Daq::dataType() const {
mutexlock lock(mutex);
assert((us)dataTypeIndex < availableDataTypes.size());
return availableDataTypes[dataTypeIndex];
}
@ -128,15 +165,16 @@ double Daq::inputRangeForChannel(us ch) const {
if (!(ch < ninchannels)) {
throw runtime_error("Invalid channel number");
}
mutexlock lock(mutex);
assert(inputRangeIndices.size() == eninchannels.size());
return availableInputRanges[inputRangeIndices[ch]];
}
us Daq::neninchannels() const {
us Daq::neninchannels(bool include_monitorchannel) const {
mutexlock lock(mutex);
us inch = std::count(eninchannels.begin(), eninchannels.end(), true);
if (monitorOutput)
if (monitorOutput && include_monitorchannel)
inch++;
return inch;
}

View File

@ -12,8 +12,8 @@
#include "lasp_cppqueue.h"
#include "string"
#include "vector"
#include <mutex>
#include <iostream>
#include <mutex>
#include <sstream>
using std::cerr;
@ -30,7 +30,7 @@ typedef vector<us> usvec;
typedef std::lock_guard<std::mutex> mutexlock;
class DataType {
public:
public:
string name;
unsigned sw;
bool is_floating;
@ -38,6 +38,9 @@ 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) {}
bool operator==(const DataType &o) {
return (name == o.name && sw == o.sw && is_floating == o.is_floating);
}
};
const DataType dtype_invalid;
@ -48,17 +51,13 @@ const DataType dtype_int16("16-bits integers", 2, false);
const DataType dtype_int32("32-bits integers", 4, false);
const std::vector<DataType> dataTypes = {
dtype_int8,
dtype_int16,
dtype_int32,
dtype_fl32,
dtype_fl64,
dtype_int8, dtype_int16, dtype_int32, dtype_fl32, dtype_fl64,
};
class DaqApi {
public:
string apiname = "";
unsigned apicode = 0;
public:
string apiname = "Invalid API";
int apicode = -1;
unsigned api_specific_subcode = 0;
DaqApi(string apiname, unsigned apicode, unsigned api_specific_subcode = 0)
@ -69,6 +68,8 @@ class DaqApi {
return (apiname == other.apiname && apicode == other.apicode &&
api_specific_subcode == other.api_specific_subcode);
}
static vector<DaqApi> getAvailableApis();
};
#ifdef HAS_ULDAQ_API
@ -76,16 +77,19 @@ const DaqApi uldaqapi("UlDaq", 0);
#endif
#ifdef HAS_RTAUDIO_API
const DaqApi rtaudioAlsaApi("RtAudio Linux ALSA", 1, RtAudio::Api::LINUX_ALSA);
const DaqApi rtaudioPulseaudioApi("RtAudio Linux Pulseaudio", 2, RtAudio::Api::LINUX_PULSE);
const DaqApi rtaudioWasapiApi("RtAudio Windows Wasapi", 3, RtAudio::Api::WINDOWS_WASAPI);
const DaqApi rtaudioDsApi("RtAudio Windows DirectSound", 4, RtAudio::Api::WINDOWS_DS);
const DaqApi rtaudioAsioApi("RtAudio Windows ASIO", 4, RtAudio::Api::WINDOWS_ASIO);
const DaqApi rtaudioPulseaudioApi("RtAudio Linux Pulseaudio", 2,
RtAudio::Api::LINUX_PULSE);
const DaqApi rtaudioWasapiApi("RtAudio Windows Wasapi", 3,
RtAudio::Api::WINDOWS_WASAPI);
const DaqApi rtaudioDsApi("RtAudio Windows DirectSound", 4,
RtAudio::Api::WINDOWS_DS);
const DaqApi rtaudioAsioApi("RtAudio Windows ASIO", 5,
RtAudio::Api::WINDOWS_ASIO);
#endif
// Structure containing device info parameters
class DeviceInfo {
public:
public:
DaqApi api;
string device_name = "";
@ -114,7 +118,7 @@ class DeviceInfo {
/* datatype(dtype_invalid) { } */
double prefSampleRate() const {
if (((us) prefSampleRateIndex < availableSampleRates.size()) &&
if (((us)prefSampleRateIndex < availableSampleRates.size()) &&
(prefSampleRateIndex >= 0)) {
return availableSampleRates[prefSampleRateIndex];
} else {
@ -133,15 +137,16 @@ class DeviceInfo {
// Device configuration parameters
class DaqConfiguration {
public:
public:
DaqApi api;
string device_name;
boolvec eninchannels; // Enabled input channels
boolvec eninchannels; // Enabled input channelsvice(const DeviceInfo& devinfo,
boolvec enoutchannels; // Enabled output channels
vector<double> inchannel_sensitivities;
vector<string> inchannel_names;
vector<string> inchannel_qtys;
// This is not necessary at the moment
/* vector<double> outchannel_sensitivities; */
@ -154,9 +159,7 @@ class DaqConfiguration {
us framesPerBlockIndex = 0;
bool monitorOutput = false; // Whether the first output channel should be replicated
// to the input as the first channel
bool monitorOutput = false;
boolvec inputIEPEEnabled;
boolvec inputACCouplingMode;
@ -166,20 +169,26 @@ class DaqConfiguration {
// Create a default configuration, with all channels disabled on both
// input and output, and default channel names
DaqConfiguration(const DeviceInfo &device);
DaqConfiguration() {}
bool match(const DeviceInfo &devinfo) const;
int getHighestInChannel() const;
int getHighestOutChannel() const;
int getLowestInChannel() const;
int getLowestOutChannel() const;
};
class Daq;
class Daq : public DaqConfiguration,public DeviceInfo {
class Daq : public DaqConfiguration, public DeviceInfo {
mutable std::mutex mutex;
public:
public:
static vector<DeviceInfo> getDeviceInfo();
static Daq *createDevice(const DaqConfiguration &config,
const std::vector<DeviceInfo> &devinfos);
static Daq *createDaq(const DeviceInfo &, const DaqConfiguration &config);
Daq(const DeviceInfo &devinfo, const DaqConfiguration &config);
@ -191,12 +200,20 @@ class Daq : public DaqConfiguration,public DeviceInfo {
virtual bool isRunning() const = 0;
virtual ~Daq(){};
us neninchannels() const;
us neninchannels(bool include_monitorchannel = true) const;
us nenoutchannels() const;
double samplerate() const;
double inputRangeForChannel(us ch) const;
DataType dataType() const;
us framesPerBlock() const {
mutexlock lock(mutex);
return availableFramesPerBlock[framesPerBlockIndex];
}
bool duplexMode() const {
return (neninchannels(false) > 0 && nenoutchannels() > 0);
}
};
#endif // LASP_CPPDAQ_H

View File

@ -2,6 +2,9 @@
#include <RtAudio.h>
#include <atomic>
#include <thread>
#include <cstring>
#include <cassert>
using std::atomic;
void fillRtAudioDeviceInfo(vector<DeviceInfo> &devinfolist) {
@ -79,19 +82,265 @@ void fillRtAudioDeviceInfo(vector<DeviceInfo> &devinfolist) {
}
int mycallback(void *outputBuffer, void *inputBuffer,
unsigned int nFrames,
double streamTime,
RtAudioStreamStatus status,
void *userData);
void myerrorcallback(RtAudioError::Type,const string& errorText);
class AudioDaq: public Daq {
atomic<bool> stopThread;
std::thread* thread = NULL;
SafeQueue<void*> *inqueue = NULL;
SafeQueue<void*> *outqueue = NULL;
SafeQueue<void*> *outDelayqueue = NULL;
void* inbuffer = NULL;
void* outbuffer = NULL;
RtAudio* rtaudio = NULL;
RtAudio::StreamParameters* instreamparams = nullptr;
RtAudio::StreamParameters* outstreamparams = nullptr;
us nFramesPerBlock;
public:
friend void threadfcn(AudioDaq*);
AudioDaq(const DeviceInfo& devinfo,
const DaqConfiguration& config):
Daq(devinfo, config) {
nFramesPerBlock = this->framesPerBlock();
if(neninchannels(false) > 0) {
instreamparams = new RtAudio::StreamParameters();
instreamparams->nChannels = getHighestInChannel();
instreamparams->firstChannel = 0;
instreamparams->deviceId = devinfo.api_specific_devindex;
}
if(nenoutchannels() > 0) {
outstreamparams = new RtAudio::StreamParameters();
outstreamparams->nChannels = getHighestOutChannel();
outstreamparams->firstChannel = 0;
outstreamparams->deviceId = devinfo.api_specific_devindex;
}
RtAudio::StreamOptions streamoptions;
streamoptions.flags = RTAUDIO_NONINTERLEAVED | RTAUDIO_HOG_DEVICE;
streamoptions.numberOfBuffers = 2;
streamoptions.streamName = "RtAudio stream";
streamoptions.priority = 0;
RtAudioFormat format;
DataType dtype = dataType();
if(dtype == dtype_fl32) {
format = RTAUDIO_FLOAT32;
} else if(dtype == dtype_fl64) {
format = RTAUDIO_FLOAT64;
} else if(dtype == dtype_int8) {
format = RTAUDIO_SINT8;
} else if(dtype == dtype_int16) {
format = RTAUDIO_SINT16;
} else if(dtype == dtype_int32) {
format = RTAUDIO_SINT32;
} else {
throw runtime_error("Invalid data type");
}
try {
rtaudio = new RtAudio((RtAudio::Api) devinfo.api_specific_devindex);
if(!rtaudio) {
throw runtime_error("RtAudio allocation failed");
}
rtaudio->openStream(
outstreamparams,
instreamparams,
format,
(us) samplerate(),
&nFramesPerBlock,
&mycallback,
(void*) this,
&streamoptions,
&myerrorcallback
);
} catch(...) {
if(rtaudio) delete rtaudio;
throw;
}
if(monitorOutput) {
outDelayqueue = new SafeQueue<void*>();
}
}
friend int mycallback(void *outputBuffer, void *inputBuffer,
unsigned int nFrames,
double streamTime,
RtAudioStreamStatus status,
void *userData);
void start(SafeQueue<void*> *inqueue, SafeQueue<void*> *outqueue) {
if(isRunning()){
throw runtime_error("Stream already running");
}
if(neninchannels(false) > 0 && !inqueue) {
throw runtime_error("inqueue argument not given");
}
if(nenoutchannels() > 0 && !outqueue) {
throw runtime_error("outqueue argument not given");
}
assert(rtaudio);
rtaudio->startStream();
}
void stop() {
if(!isRunning()) {
cerr << "Stream is already stopped" << endl;
}
else {
assert(rtaudio);
rtaudio->stopStream();
}
}
bool isRunning() const {return (rtaudio && rtaudio->isStreamRunning());}
~AudioDaq() {
assert(rtaudio);
if(rtaudio->isStreamRunning()) {
stop();
}
if(rtaudio->isStreamOpen()) {
rtaudio->closeStream();
}
if(rtaudio) delete rtaudio;
if(outDelayqueue) delete outDelayqueue;
if(instreamparams) delete instreamparams;
if(outstreamparams) delete outstreamparams;
}
};
Daq* createRtAudioDevice(const DeviceInfo& devinfo,
const DaqConfiguration& config) {
AudioDaq *daq = NULL;
try {
daq = new AudioDaq(devinfo, config);
} catch (runtime_error &e) {
if (daq)
delete daq;
throw;
}
return daq;
}
int mycallback(
void *outputBuffervoid,
void *inputBuffervoid,
unsigned int nFrames,
double streamTime,
RtAudioStreamStatus status,
void *userData) {
u_int8_t* inputBuffer = (u_int8_t*) inputBuffervoid;
u_int8_t* outputBuffer = (u_int8_t*) outputBuffervoid;
AudioDaq* daq = (AudioDaq*) userData;
DataType dtype = daq->dataType();
/* us neninchannels = daq->neninchannels(); */
us neninchannels_inc_mon = daq->neninchannels();
us nenoutchannels = daq->nenoutchannels();
bool monitorOutput = daq->monitorOutput;
us bytesperchan = dtype.sw*nFrames;
us monitorOffset = ((us) monitorOutput)*bytesperchan;
SafeQueue<void*> *inqueue = daq->inqueue;
SafeQueue<void*> *outqueue = daq->outqueue;
SafeQueue<void*> *outDelayqueue = daq->outDelayqueue;
const boolvec& eninchannels = daq->eninchannels;
const boolvec& enoutchannels = daq->enoutchannels;
if(inputBuffer || monitorOutput) {
u_int8_t *inbuffercpy = (u_int8_t*) malloc(bytesperchan*neninchannels_inc_mon);
if(inputBuffer) {
us j=0; // OUR buffer channel counter
us i=0; // RtAudio channel counter
for(us ch=daq->getLowestInChannel();ch<=daq->getHighestInChannel();ch++) {
if(eninchannels[ch]) {
memcpy(
&(inbuffercpy[monitorOffset+j*bytesperchan]),
&(inputBuffer[i*bytesperchan]),
bytesperchan);
j++;
}
i++;
}
}
if(monitorOutput) {
assert(outDelayqueue);
if(!daq->outDelayqueue->empty()) {
void* dat = daq->outDelayqueue->dequeue();
memcpy((void*) inbuffercpy, dat, bytesperchan);
free(dat);
} else {
cerr << "Warning: output delay queue appears empty!" << endl;
memset(inbuffercpy, 0, bytesperchan);
}
}
assert(inqueue);
inqueue->enqueue(inbuffercpy);
}
if(outputBuffer) {
assert(outqueue);
if(!outqueue->empty()) {
u_int8_t* outbuffercpy = (u_int8_t*) outqueue->dequeue();
us j=0; // OUR buffer channel counter
us i=0; // RtAudio channel counter
for(us ch=0;ch<=daq->getHighestOutChannel();ch++) {
if(enoutchannels[ch]) {
memcpy(
&(outputBuffer[i*bytesperchan]),
&(outbuffercpy[j*bytesperchan]),
bytesperchan);
j++;
}
else {
memset(
&(outputBuffer[i*bytesperchan]),0,bytesperchan);
}
i++;
}
if(!monitorOutput) {
free(outbuffercpy);
} else {
assert(outDelayqueue);
outDelayqueue->enqueue((void*) outbuffercpy);
}
}
else {
cerr << "Stream output buffer underflow, zero-ing buffer... " << endl;
}
}
return 0;
}
void myerrorcallback(RtAudioError::Type,const string& errorText) {
cerr << errorText << endl;
}

View File

@ -1,2 +1,10 @@
include "lasp_common_decls.pxd"
ctypedef struct PyStreamData
cdef class Daq:
cdef:
PyStreamData *sd
cppDaq* daq_device
cdef cleanupStream(self, PyStreamData* sd)

View File

@ -1,11 +1,27 @@
cimport cython
from .lasp_deviceinfo cimport DeviceInfo
from .lasp_daqconfig cimport DaqConfiguration
from cpython.ref cimport PyObject,Py_INCREF, Py_DECREF
from .lasp_device_common import AvType
__all__ = ['Daq']
cdef cnp.NPY_TYPES getNumpyDataType(DataType& dt):
if(dt == dtype_fl32):
return cnp.NPY_FLOAT32
elif(dt == dtype_fl64):
return cnp.NPY_FLOAT64
elif(dt == dtype_int8):
return cnp.NPY_INT8
elif(dt == dtype_int16):
return cnp.NPY_INT16
elif(dt == dtype_int32):
return cnp.NPY_INT32
else:
raise ValueError('Unknown data type')
ctypedef struct PyStreamData:
PyObject* pyCallback
@ -24,6 +40,8 @@ ctypedef struct PyStreamData:
double samplerate
cnp.NPY_TYPES npy_format
# If either of these queue pointers are NULL, it means the stream does not have an
# input, or output.
SafeQueue[void*] *inQueue
@ -33,7 +51,6 @@ ctypedef struct PyStreamData:
cdef void audioCallbackPythonThreadFunction(void* voidsd) nogil:
cdef:
PyStreamData* sd = <PyStreamData*> voidsd
cnp.NPY_TYPES npy_format
double* inbuffer = NULL
double* outbuffer = NULL
@ -43,8 +60,6 @@ cdef void audioCallbackPythonThreadFunction(void* voidsd) nogil:
unsigned nBytesPerChan= sd.nBytesPerChan
unsigned nFramesPerBlock= sd.nFramesPerBlock
unsigned sw = sizeof(double)
double sleeptime = (<double> sd.nFramesPerBlock)/(4*sd.samplerate);
us sleeptime_us = <us> (sleeptime*1e6);
@ -64,11 +79,10 @@ cdef void audioCallbackPythonThreadFunction(void* voidsd) nogil:
outbuffer,
nFramesPerBlock,
noutchannels,
npy_format,
sd.npy_format,
False, # Do not transfer ownership
True) # F-contiguous
try:
rval = callback(None,
npy_output,
nFramesPerBlock,
@ -93,10 +107,9 @@ cdef void audioCallbackPythonThreadFunction(void* voidsd) nogil:
inbuffer,
nFramesPerBlock,
ninchannels,
npy_format,
sd.npy_format,
True, # Do transfer ownership
True) # F-contiguous is True: data is Fortran-cont.
rval = callback(npy_input,
None,
nFramesPerBlock,
@ -118,9 +131,6 @@ cdef void audioCallbackPythonThreadFunction(void* voidsd) nogil:
fprintf(stderr, 'Exiting python thread...\n')
cdef class Daq:
cdef:
PyStreamData *sd
cppDaq* daq_device
def __cinit__(self):
@ -132,9 +142,9 @@ cdef class Daq:
self.sd = NULL
def __dealloc__(self):
fprintf(stderr, "UlDaq.__dealloc__\n")
# fprintf(stderr, "UlDaq.__dealloc__\n")
if self.sd is not NULL:
fprintf(stderr, "UlDaq.__dealloc__, stopping stream.\n")
fprintf(stderr, "UlDaq.__dealloc__: stopping stream.\n")
self.stop()
def isRunning(self):
@ -144,143 +154,101 @@ cdef class Daq:
def getDeviceInfo():
cdef:
vector[cppDeviceInfo] devinfos = cppDaq.getDeviceInfo()
pydevinfo = []
pydevinfo = {}
for i in range(devinfos.size()):
d = DeviceInfo()
d.devinfo = <cppDeviceInfo> devinfos[i]
pydevinfo.append(d)
if d.api not in pydevinfo.keys():
pydevinfo[d.api] = [d]
else:
pydevinfo[d.api].append(d)
return pydevinfo
# @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
"""
cdef:
DaqConfiguration pydaqconfig
DeviceInfo pydevinfo
cppDaqConfiguration* daqconfig
cppDeviceInfo* devinfo
vector[cppDeviceInfo] devinfos
# 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
pydaqconfig = avstream.daqconfig
avtype = avstream.avtype
pydevinfo = avstream.deviceinfo
# cdef:
# bint duplex_mode = daqconfig.duplex_mode
# bint monitorOutput = daqconfig.monitor_gen
daqconfig = &(pydaqconfig.config)
devinfo = &(pydevinfo.devinfo)
# unsigned int nFramesPerBlock = daqconfig.nFramesPerBlock
# unsigned int samplerate
self.daq_device = cppDaq.createDaq(devinfo[0], daqconfig[0])
cdef:
cppDaq* daq = self.daq_device
# int i
# bint in_stream=False
# bint out_stream=False
cdef:
unsigned int nFramesPerBlock = daq.framesPerBlock()
double samplerate = daq.samplerate()
DataType dtype = daq.dataType()
# cppDeviceInfo devinfo
# DaqConfiguration cppconfig
# cppDaq* daq_device
# if daqconfig.nFramesPerBlock > 8192 or daqconfig.nFramesPerBlock < 512:
# raise ValueError('Invalid number of nFramesPerBlock')
if nFramesPerBlock > 8192 or nFramesPerBlock < 512:
del self.daq_device
raise ValueError('Invalid number of nFramesPerBlock')
# # if daqconfig.outputDelayBlocks != 0:
# # print('WARNING: OutputDelayBlocks not supported by API')
# # 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:
del daq
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.thread = NULL
self.sd.samplerate = <double> samplerate
# self.sd.stopThread.store(False)
# self.sd.inQueue = NULL
# self.sd.outQueue = NULL
self.sd.ninchannels = daq.neninchannels()
self.sd.noutchannels = daq.nenoutchannels()
self.sd.nBytesPerChan = nFramesPerBlock*dtype.sw
self.sd.nFramesPerBlock = nFramesPerBlock
self.sd.npy_format = getNumpyDataType(dtype)
# self.sd.thread = NULL
# self.sd.samplerate = <double> samplerate
if daq.neninchannels() > 0:
self.sd.inQueue = new SafeQueue[void*]()
if daq.nenoutchannels() > 0:
self.sd.inQueue = new SafeQueue[void*]()
# self.sd.ninchannels = 0
# self.sd.noutchannels = 0
# self.sd.nBytesPerChan = daqconfig.nFramesPerBlock*sizeof(double)
# self.sd.nFramesPerBlock = daqconfig.nFramesPerBlock
self.sd.pyCallback = <PyObject*> avstream._audioCallback
# Increase reference count to the callback
Py_INCREF(<object> avstream._audioCallback)
# # 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()]
with nogil:
self.sd.thread = new CPPThread[void*, void (*)(void*)](audioCallbackPythonThreadFunction,
<void*> self.sd)
# self.sd.inQueue = new SafeQueue[void*]()
# Allow stream stome time to start
CPPsleep_ms(500)
# # 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.daq_device.start(
self.sd.inQueue,
self.sd.outQueue)
# self.sd.outQueue = new SafeQueue[void*]()
# daq_device = createDaqDevice(devinfo, cppconfig)
# self.sd.pyCallback = <PyObject*> avstream._audioCallback
# self.sd.ninchannels = daq_device.neninchannels()
# self.sd.noutchannels = daq_device.nenoutchannels()
# self.daq_device = daq_device
# # 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)
# # Allow it to start
# CPPsleep_ms(500)
# 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:

View File

@ -0,0 +1,5 @@
include "lasp_common_decls.pxd"
cdef class DaqConfiguration:
cdef:
cppDaqConfiguration config

View File

@ -13,6 +13,7 @@ from typing import List
from dataclasses_json import dataclass_json
from ..lasp_common import lasp_shelve, SIQtys, Qty
import json
class CouplingMode:
@ -25,91 +26,106 @@ class Range:
tenV = '+/- 10 V'
undefined = 'Undefined'
@dataclass_json
@dataclass
class DAQChannel:
class DaqChannel:
channel_enabled: bool
channel_name: str = 'Unnamed channel'
sensitivity: float = 1.0
qty: Qty = SIQtys.default
range_: str = 'Undefined'
range_index: int = 0
ACCoupling_enabled: bool = False
IEPE_enabled: bool = False
cdef class DAQConfiguration:
cdef class DaqConfiguration:
"""
Initialize a device descriptor
"""
# DaqConfiguration config
def getInputChannels(self):
return self.input_channel_configs
@staticmethod
def from_json(self, jsonstring):
config_dict = json.loads(jsonstring)
return DaqConfiguration.from_dict(config_dict)
def getOutputChannels(self):
return self.output_channel_configs
@staticmethod
def from_dict(pydict):
cdef:
cppDaqConfiguration config
vector[DaqApi] apis = DaqApi.getAvailableApis()
config.api = apis[pydict['apicode']]
config.device_name = pydict['device_name'].encode('utf-8')
config.eninchannels = pydict['eninchannels']
config.enoutchannels = pydict['enoutchannels']
config.inchannel_names = pydict['inchannel_names']
config.outchannel_names = pydict['outchannel_names']
config.sampleRateIndex = pydict['sampleRateIndex']
config.framesPerBlockIndex = pydict['framesPerBlockIndex']
config.dataTypeIndex = pydict['dataTypeIndex']
config.monitorOutput = pydict['monitorOutput']
config.inputIEPEEnabled = pydict['inputIEPEEnabled']
config.inputACCouplingMode = pydict['inputACCouplingMode']
config.inputRangeIndices = pydict['inputRangeIndices']
def firstEnabledOutputChannelNumber(self):
"""
Returns the channel number of the first enabled output channel. Returns -1 if
no channels are enabled.
"""
for i, ch in enumerate(self.output_channel_configs):
if ch.channel_enabled:
return i
return -1
pydaqcfg = DaqConfiguration()
pydaqcfg.config = config
def lastEnabledInputChannelNumber(self):
last = -1
for i, ch in enumerate(self.input_channel_configs):
if ch.channel_enabled:
last = i
return last
return pydaqcfg
def lastEnabledOutputChannelNumber(self):
last = -1
for i, ch in enumerate(self.output_channel_configs):
print(ch)
if ch.channel_enabled:
last = i
return last
def to_json(self):
return json.dumps(dict(
apicode = self.api.apicode,
device_name = self.config.device_name.decode('utf-8'),
def getEnabledInputChannels(self):
en_channels = []
for chan in self.input_channel_configs:
if chan.channel_enabled:
en_channels.append(chan)
return en_channels
eninchannels = self.eninchannels(),
enoutchannels = self.enoutchannels(),
def getEnabledInputChannelNames(self):
return [ch.channel_name for ch in self.getEnabledInputChannels()]
inchannel_names = [name.decode('utf-8') for name in
self.config.inchannel_names],
outchannel_names = [name.decode('utf-8') for name in
self.config.outchannel_names],
sampleRateIndex = self.config.sampleRateIndex,
dataTypeIndex = self.config.dataTypeIndex,
nFramesPerBlockIndex = self.config.framesPerBlockIndex,
monitorOutput = self.config.monitorOutput,
def getEnabledInputChannelQtys(self):
return [ch.qty for ch in self.getEnabledInputChannels()]
inputIEPEEnabled = self.config.inputIEPEEnabled,
inputACCouplingMode = self.config.inputACCouplingMode,
inputRangeIndices = self.config.inputRangeIndices,
))
def getEnabledInputChannelSensitivities(self):
return [
float(channel.sensitivity)
for channel in self.getEnabledInputChannels()
]
def getInChannel(self, i:int):
return DaqChannel(
channel_enabled=self.config.eninchannels[i],
channel_name=self.config.inchannel_names[i].decode('utf-8'),
sensitivity=self.config.inchannel_sensitivities[i],
range_index=self.config.inputRangeIndices[i],
ACCoupling_enabled=self.config.inputACCouplingMode[i],
IEPE_enabled=self.config.inputIEPEEnabled[i]
)
def getOutChannel(self, i:int):
return DaqChannel(
channel_enabled=self.config.enoutchannels[i],
channel_name=self.config.outchannel_names[i].decode('utf-8'),
)
def setInChannel(self, i:int, daqchannel: DaqChannel):
self.config.eninchannels[i] = daqchannel.channel_enabled
self.config.inchannel_names[i] = daqchannel.channel_name
self.config.inchannel_sensitivities[i] = daqchannel.sensitivity
self.config.inputRangeIndices[i] = daqchannel.range_index
self.config.inputACCouplingMode[i] = daqchannel.ACCoupling_enabled
self.config.inputIEPEEnabled[i] = daqchannel.IEPE_enabled
def getEnabledOutputChannels(self):
en_channels = []
for chan in self.output_channel_configs:
if chan.channel_enabled:
en_channels.append(chan)
return en_channels
def setOutChannel(self, i:int, daqchannel: DaqChannel):
self.config.enoutchannels[i] = daqchannel.channel_enabled
self.config.outchannel_names[i] = daqchannel.channel_name
def getEnabledOutputChannelQtys(self):
return [ch.qty for ch in self.getEnabledOutputChannels()]
def eninchannels(self):
return self.config.eninchannels
def getEnabledOutputChannelSensitivities(self):
return [
float(channel.sensitivity)
for channel in self.getEnabledOutputChannels()
]
def enoutchannels(self):
return self.config.enoutchannels
@staticmethod
def loadConfigsJSON():
@ -122,14 +138,14 @@ cdef class DAQConfiguration:
"""
Returns a list of currently available configurations
"""
configs_json = DAQConfiguration.loadConfigsJSON()
configs_json = DaqConfiguration.loadConfigsJSON()
configs = {}
for name, val in configs_json.items():
configs[name] = DAQConfiguration.from_json(val)
configs[name] = DaqConfiguration.from_json(val)
return configs
def saveConfig(self, name):
configs_json = DAQConfiguration.loadConfigsJSON()
configs_json = DaqConfiguration.loadConfigsJSON()
with lasp_shelve() as sh:
configs_json[name] = self.to_json()
@ -138,6 +154,6 @@ cdef class DAQConfiguration:
@staticmethod
def deleteConfig(name):
with lasp_shelve() as sh:
cur_configs = DAQConfiguration.loadConfigs()
cur_configs = DaqConfiguration.loadConfigs()
del cur_configs[name]
sh.store('daqconfigs', cur_configs)

View File

@ -48,7 +48,7 @@ import os
import time
import wave
from .lasp_common import SIQtys, Qty
from .device import DAQChannel
from .device import DaqChannel
import logging
@ -217,7 +217,7 @@ class Measurement:
@property
def channelConfig(self):
return [DAQChannel(channel_enabled=True,
return [DaqChannel(channel_enabled=True,
channel_name=chname,
sensitivity=sens,
qty=qty)