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(cpp_daq_files lasp_cppdaq.cpp)
set(mycpp_daq_linklibs pthread) set(cpp_daq_linklibs pthread)
if(LASP_RTAUDIO) if(LASP_RTAUDIO)
include_directories(/usr/include/rtaudio) include_directories(/usr/include/rtaudio)
list(APPEND cpp_daq_files lasp_cpprtaudio.cpp) list(APPEND cpp_daq_files lasp_cpprtaudio.cpp)
list(PREPEND mycpp_daq_linklibs rtaudio) list(PREPEND cpp_daq_linklibs rtaudio)
endif() endif()
if(LASP_ULDAQ) if(LASP_ULDAQ)
list(APPEND cpp_daq_files lasp_cppuldaq.cpp) list(APPEND cpp_daq_files lasp_cppuldaq.cpp)
list(PREPEND mycpp_daq_linklibs uldaq) list(PREPEND cpp_daq_linklibs uldaq)
endif() endif()
if(win32) if(win32)
list(APPEND mycpp_daq_linklibs python) list(APPEND cpp_daq_linklibs python)
endif(win32) endif(win32)
add_library(cpp_daq ${cpp_daq_files}) 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) 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) 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() endforeach()
@ -36,4 +36,4 @@ endforeach()
# also used in the testing directory. But better to already link cpp_daq with # also used in the testing directory. But better to already link cpp_daq with
# linklibs. # 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 apicode
unsigned api_specific_subcode unsigned api_specific_subcode
@staticmethod
vector[DaqApi] getAvailableApis();
cdef cppclass DataType: cdef cppclass DataType:
string name string name
unsigned sw unsigned sw
bool is_floating bool is_floating
DataType dtype_fl64 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": cdef cppclass cppDeviceInfo "DeviceInfo":
DaqApi api DaqApi api
string device_name 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 int prefSampleRateIndex
vector[us] availableFramesPerBlock
unsigned prefFramesPerBlockIndex
dvec availableInputRanges
int prefInputRangeIndex int prefInputRangeIndex
int prefFramesPerBlockIndex
unsigned ninchannels unsigned ninchannels
unsigned noutchannels unsigned noutchannels
bool hasInputIEPE bool hasInputIEPE
bool hasInputACCouplingSwitch bool hasInputACCouplingSwitch
bool hasInputTrigger bool hasInputTrigger
vector[double] availableInputRanges
cdef cppclass DaqConfiguration: cdef cppclass cppDaqConfiguration "DaqConfiguration":
DaqApi api
string device_name
boolvec eninchannels boolvec eninchannels
boolvec enoutchannels boolvec enoutchannels
vector[string] channel_names
vector[double] channel_sensitivities
unsigned sampleRateIndex
DataType datatype
bool monitorOutput
unsigned nFramesPerBlock;
boolvec inputIEPEEnabled; vector[double] inchannel_sensitivities
boolvec inputACCouplingMode; vector[string] inchannel_names
usvec inputRangeIndices;
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": cdef cppclass cppDaq "Daq":
void start(SafeQueue[void*] *inQueue, void start(SafeQueue[void*] *inQueue,
SafeQueue[void*] *outQueue) except + SafeQueue[void*] *outQueue) except +
void stop() void stop() except +
double samplerate() double samplerate()
us neninchannels() us neninchannels(bool include_monitorchannel=False)
us nenoutchannels() us nenoutchannels()
DataType getDataType() DataType dataType()
us framesPerBlock()
bool isRunning()
bool duplexMode()
@staticmethod @staticmethod
cppDaq* createDaqDevice(cppDeviceInfo&, DaqConfiguration&) cppDaq* createDaq(cppDeviceInfo&, cppDaqConfiguration&) except +
@staticmethod @staticmethod
vector[cppDeviceInfo] getDeviceInfo() vector[cppDeviceInfo] getDeviceInfo() except +

View File

@ -24,6 +24,24 @@ vector<DeviceInfo> Daq::getDeviceInfo() {
return devs; 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) { DaqConfiguration::DaqConfiguration(const DeviceInfo &device) {
api = device.api; api = device.api;
@ -61,19 +79,37 @@ bool DaqConfiguration::match(const DeviceInfo& dev) const {
return (dev.device_name == device_name && dev.api == api); return (dev.device_name == device_name && dev.api == api);
} }
Daq *Daq::createDevice(const DaqConfiguration &config, int DaqConfiguration::getHighestInChannel() const {
const vector<DeviceInfo> &devinfos) { for(int i=eninchannels.size()-1; i>-1;i--) {
if(eninchannels[i]) return i;
bool match;
DeviceInfo devinfo;
for (auto cur_devinfo : devinfos) {
if ((match = config.match(cur_devinfo))) {
devinfo = cur_devinfo;
break;
}
} }
if (!match) { return -1;
return NULL; }
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;
}
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 // Some basic sanity checks
@ -112,13 +148,14 @@ Daq::Daq(const DeviceInfo &devinfo, const DaqConfiguration &config)
double Daq::samplerate() const { double Daq::samplerate() const {
mutexlock lock(mutex);
assert(sampleRateIndex < availableSampleRates.size()); assert(sampleRateIndex < availableSampleRates.size());
return availableSampleRates[sampleRateIndex]; return availableSampleRates[sampleRateIndex];
} }
DataType Daq::dataType() const { DataType Daq::dataType() const {
mutexlock lock(mutex);
assert((us)dataTypeIndex < availableDataTypes.size()); assert((us)dataTypeIndex < availableDataTypes.size());
return availableDataTypes[dataTypeIndex]; return availableDataTypes[dataTypeIndex];
} }
@ -128,15 +165,16 @@ double Daq::inputRangeForChannel(us ch) const {
if (!(ch < ninchannels)) { if (!(ch < ninchannels)) {
throw runtime_error("Invalid channel number"); throw runtime_error("Invalid channel number");
} }
mutexlock lock(mutex);
assert(inputRangeIndices.size() == eninchannels.size()); assert(inputRangeIndices.size() == eninchannels.size());
return availableInputRanges[inputRangeIndices[ch]]; return availableInputRanges[inputRangeIndices[ch]];
} }
us Daq::neninchannels() const { us Daq::neninchannels(bool include_monitorchannel) const {
mutexlock lock(mutex); mutexlock lock(mutex);
us inch = std::count(eninchannels.begin(), eninchannels.end(), true); us inch = std::count(eninchannels.begin(), eninchannels.end(), true);
if (monitorOutput) if (monitorOutput && include_monitorchannel)
inch++; inch++;
return inch; return inch;
} }

View File

@ -12,8 +12,8 @@
#include "lasp_cppqueue.h" #include "lasp_cppqueue.h"
#include "string" #include "string"
#include "vector" #include "vector"
#include <mutex>
#include <iostream> #include <iostream>
#include <mutex>
#include <sstream> #include <sstream>
using std::cerr; using std::cerr;
@ -30,14 +30,17 @@ typedef vector<us> usvec;
typedef std::lock_guard<std::mutex> mutexlock; typedef std::lock_guard<std::mutex> mutexlock;
class DataType { class DataType {
public: public:
string name; string name;
unsigned sw; unsigned sw;
bool is_floating; bool is_floating;
DataType(const char *name, unsigned sw, bool is_floating) DataType(const char *name, unsigned sw, bool is_floating)
: name(name), sw(sw), is_floating(is_floating) {} : name(name), sw(sw), is_floating(is_floating) {}
DataType() : name("invalid data type"), sw(0), is_floating(false) {} 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; const DataType dtype_invalid;
@ -48,27 +51,25 @@ const DataType dtype_int16("16-bits integers", 2, false);
const DataType dtype_int32("32-bits integers", 4, false); const DataType dtype_int32("32-bits integers", 4, false);
const std::vector<DataType> dataTypes = { const std::vector<DataType> dataTypes = {
dtype_int8, dtype_int8, dtype_int16, dtype_int32, dtype_fl32, dtype_fl64,
dtype_int16,
dtype_int32,
dtype_fl32,
dtype_fl64,
}; };
class DaqApi { class DaqApi {
public: public:
string apiname = ""; string apiname = "Invalid API";
unsigned apicode = 0; int apicode = -1;
unsigned api_specific_subcode = 0; unsigned api_specific_subcode = 0;
DaqApi(string apiname, unsigned apicode, unsigned api_specific_subcode = 0) DaqApi(string apiname, unsigned apicode, unsigned api_specific_subcode = 0)
: apiname(apiname), apicode(apicode), : apiname(apiname), apicode(apicode),
api_specific_subcode(api_specific_subcode) {} api_specific_subcode(api_specific_subcode) {}
DaqApi() {} DaqApi() {}
bool operator==(const DaqApi &other) const { bool operator==(const DaqApi &other) const {
return (apiname == other.apiname && apicode == other.apicode && return (apiname == other.apiname && apicode == other.apicode &&
api_specific_subcode == other.api_specific_subcode); api_specific_subcode == other.api_specific_subcode);
} }
static vector<DaqApi> getAvailableApis();
}; };
#ifdef HAS_ULDAQ_API #ifdef HAS_ULDAQ_API
@ -76,127 +77,143 @@ const DaqApi uldaqapi("UlDaq", 0);
#endif #endif
#ifdef HAS_RTAUDIO_API #ifdef HAS_RTAUDIO_API
const DaqApi rtaudioAlsaApi("RtAudio Linux ALSA", 1, RtAudio::Api::LINUX_ALSA); const DaqApi rtaudioAlsaApi("RtAudio Linux ALSA", 1, RtAudio::Api::LINUX_ALSA);
const DaqApi rtaudioPulseaudioApi("RtAudio Linux Pulseaudio", 2, RtAudio::Api::LINUX_PULSE); const DaqApi rtaudioPulseaudioApi("RtAudio Linux Pulseaudio", 2,
const DaqApi rtaudioWasapiApi("RtAudio Windows Wasapi", 3, RtAudio::Api::WINDOWS_WASAPI); RtAudio::Api::LINUX_PULSE);
const DaqApi rtaudioDsApi("RtAudio Windows DirectSound", 4, RtAudio::Api::WINDOWS_DS); const DaqApi rtaudioWasapiApi("RtAudio Windows Wasapi", 3,
const DaqApi rtaudioAsioApi("RtAudio Windows ASIO", 4, RtAudio::Api::WINDOWS_ASIO); 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 #endif
// Structure containing device info parameters // Structure containing device info parameters
class DeviceInfo { class DeviceInfo {
public: public:
DaqApi api; DaqApi api;
string device_name = ""; string device_name = "";
int api_specific_devindex = -1; int api_specific_devindex = -1;
vector<DataType> availableDataTypes; vector<DataType> availableDataTypes;
int prefDataTypeIndex = 0; int prefDataTypeIndex = 0;
vector<double> availableSampleRates; vector<double> availableSampleRates;
int prefSampleRateIndex = -1; int prefSampleRateIndex = -1;
vector<us> availableFramesPerBlock; vector<us> availableFramesPerBlock;
unsigned prefFramesPerBlockIndex = 0; unsigned prefFramesPerBlockIndex = 0;
dvec availableInputRanges; dvec availableInputRanges;
int prefInputRangeIndex = 0; int prefInputRangeIndex = 0;
unsigned ninchannels = 0; unsigned ninchannels = 0;
unsigned noutchannels = 0; unsigned noutchannels = 0;
bool hasInputIEPE = false; bool hasInputIEPE = false;
bool hasInputACCouplingSwitch = false; bool hasInputACCouplingSwitch = false;
bool hasInputTrigger = false; bool hasInputTrigger = false;
/* DeviceInfo(): */ /* DeviceInfo(): */
/* datatype(dtype_invalid) { } */ /* datatype(dtype_invalid) { } */
double prefSampleRate() const { double prefSampleRate() const {
if (((us) prefSampleRateIndex < availableSampleRates.size()) && if (((us)prefSampleRateIndex < availableSampleRates.size()) &&
(prefSampleRateIndex >= 0)) { (prefSampleRateIndex >= 0)) {
return availableSampleRates[prefSampleRateIndex]; return availableSampleRates[prefSampleRateIndex];
} else { } else {
throw std::runtime_error("No prefered sample rate available"); throw std::runtime_error("No prefered sample rate available");
}
} }
}
operator string() const { operator string() const {
std::stringstream str; std::stringstream str;
str << api.apiname + " " << api_specific_devindex str << api.apiname + " " << api_specific_devindex
<< " number of input channels: " << ninchannels << " number of input channels: " << ninchannels
<< " number of output channels: " << noutchannels; << " number of output channels: " << noutchannels;
return str.str(); return str.str();
} }
}; };
// Device configuration parameters // Device configuration parameters
class DaqConfiguration { class DaqConfiguration {
public: public:
DaqApi api; DaqApi api;
string device_name; string device_name;
boolvec eninchannels; // Enabled input channels boolvec eninchannels; // Enabled input channelsvice(const DeviceInfo& devinfo,
boolvec enoutchannels; // Enabled output channels boolvec enoutchannels; // Enabled output channels
vector<double> inchannel_sensitivities; vector<double> inchannel_sensitivities;
vector<string> inchannel_names; vector<string> inchannel_names;
vector<string> inchannel_qtys;
// This is not necessary at the moment // This is not necessary at the moment
/* vector<double> outchannel_sensitivities; */ /* vector<double> outchannel_sensitivities; */
vector<string> outchannel_names; vector<string> outchannel_names;
us sampleRateIndex = 0; // Index in list of sample rates us sampleRateIndex = 0; // Index in list of sample rates
us dataTypeIndex = 0; // Required datatype for output, should be us dataTypeIndex = 0; // Required datatype for output, should be
// present in the list // present in the list
us framesPerBlockIndex = 0; us framesPerBlockIndex = 0;
bool monitorOutput = false; // Whether the first output channel should be replicated bool monitorOutput = false;
// to the input as the first channel
boolvec inputIEPEEnabled;
boolvec inputACCouplingMode;
boolvec inputIEPEEnabled; usvec inputRangeIndices;
boolvec inputACCouplingMode;
usvec inputRangeIndices; // Create a default configuration, with all channels disabled on both
// input and output, and default channel names
DaqConfiguration(const DeviceInfo &device);
DaqConfiguration() {}
// Create a default configuration, with all channels disabled on both bool match(const DeviceInfo &devinfo) const;
// input and output, and default channel names
DaqConfiguration(const DeviceInfo &device);
bool match(const DeviceInfo &devinfo) const; int getHighestInChannel() const;
int getHighestOutChannel() const;
int getLowestInChannel() const;
int getLowestOutChannel() const;
}; };
class Daq; class Daq;
class Daq : public DaqConfiguration,public DeviceInfo { class Daq : public DaqConfiguration, public DeviceInfo {
mutable std::mutex mutex; mutable std::mutex mutex;
public: public:
static vector<DeviceInfo> getDeviceInfo(); static vector<DeviceInfo> getDeviceInfo();
static Daq *createDevice(const DaqConfiguration &config, static Daq *createDaq(const DeviceInfo &, const DaqConfiguration &config);
const std::vector<DeviceInfo> &devinfos);
Daq(const DeviceInfo &devinfo, const DaqConfiguration &config); Daq(const DeviceInfo &devinfo, const DaqConfiguration &config);
virtual void start(SafeQueue<void *> *inqueue, virtual void start(SafeQueue<void *> *inqueue,
SafeQueue<void *> *outqueue) = 0; SafeQueue<void *> *outqueue) = 0;
virtual void stop() = 0; virtual void stop() = 0;
virtual bool isRunning() const = 0; virtual bool isRunning() const = 0;
virtual ~Daq(){}; virtual ~Daq(){};
us neninchannels() const; us neninchannels(bool include_monitorchannel = true) const;
us nenoutchannels() const; us nenoutchannels() const;
double samplerate() const; double samplerate() const;
double inputRangeForChannel(us ch) const; double inputRangeForChannel(us ch) const;
DataType dataType() 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 #endif // LASP_CPPDAQ_H

View File

@ -2,6 +2,9 @@
#include <RtAudio.h> #include <RtAudio.h>
#include <atomic> #include <atomic>
#include <thread> #include <thread>
#include <cstring>
#include <cassert>
using std::atomic; using std::atomic;
void fillRtAudioDeviceInfo(vector<DeviceInfo> &devinfolist) { 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 { class AudioDaq: public Daq {
atomic<bool> stopThread; SafeQueue<void*> *inqueue = NULL;
SafeQueue<void*> *outqueue = NULL;
SafeQueue<void*> *outDelayqueue = NULL;
std::thread* thread = NULL; RtAudio* rtaudio = NULL;
RtAudio::StreamParameters* instreamparams = nullptr;
RtAudio::StreamParameters* outstreamparams = nullptr;
SafeQueue<void*> *inqueue = NULL; us nFramesPerBlock;
SafeQueue<void*> *outqueue = NULL;
void* inbuffer = NULL; public:
void* outbuffer = NULL; AudioDaq(const DeviceInfo& devinfo,
const DaqConfiguration& config):
Daq(devinfo, config) {
public: nFramesPerBlock = this->framesPerBlock();
friend void threadfcn(AudioDaq*);
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" 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 cimport cython
from .lasp_deviceinfo cimport DeviceInfo from .lasp_deviceinfo cimport DeviceInfo
from .lasp_daqconfig cimport DaqConfiguration
from cpython.ref cimport PyObject,Py_INCREF, Py_DECREF from cpython.ref cimport PyObject,Py_INCREF, Py_DECREF
from .lasp_device_common import AvType from .lasp_device_common import AvType
__all__ = ['Daq'] __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: ctypedef struct PyStreamData:
PyObject* pyCallback PyObject* pyCallback
@ -24,6 +40,8 @@ ctypedef struct PyStreamData:
double samplerate double samplerate
cnp.NPY_TYPES npy_format
# If either of 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. # input, or output.
SafeQueue[void*] *inQueue SafeQueue[void*] *inQueue
@ -33,7 +51,6 @@ ctypedef struct PyStreamData:
cdef void audioCallbackPythonThreadFunction(void* voidsd) nogil: cdef void audioCallbackPythonThreadFunction(void* voidsd) nogil:
cdef: cdef:
PyStreamData* sd = <PyStreamData*> voidsd PyStreamData* sd = <PyStreamData*> voidsd
cnp.NPY_TYPES npy_format
double* inbuffer = NULL double* inbuffer = NULL
double* outbuffer = NULL double* outbuffer = NULL
@ -43,8 +60,6 @@ cdef void audioCallbackPythonThreadFunction(void* voidsd) nogil:
unsigned nBytesPerChan= sd.nBytesPerChan unsigned nBytesPerChan= sd.nBytesPerChan
unsigned nFramesPerBlock= sd.nFramesPerBlock unsigned nFramesPerBlock= sd.nFramesPerBlock
unsigned sw = sizeof(double)
double sleeptime = (<double> sd.nFramesPerBlock)/(4*sd.samplerate); double sleeptime = (<double> sd.nFramesPerBlock)/(4*sd.samplerate);
us sleeptime_us = <us> (sleeptime*1e6); us sleeptime_us = <us> (sleeptime*1e6);
@ -64,11 +79,10 @@ cdef void audioCallbackPythonThreadFunction(void* voidsd) nogil:
outbuffer, outbuffer,
nFramesPerBlock, nFramesPerBlock,
noutchannels, noutchannels,
npy_format, sd.npy_format,
False, # Do not transfer ownership False, # Do not transfer ownership
True) # F-contiguous True) # F-contiguous
try: try:
rval = callback(None, rval = callback(None,
npy_output, npy_output,
nFramesPerBlock, nFramesPerBlock,
@ -93,10 +107,9 @@ cdef void audioCallbackPythonThreadFunction(void* voidsd) nogil:
inbuffer, inbuffer,
nFramesPerBlock, nFramesPerBlock,
ninchannels, ninchannels,
npy_format, sd.npy_format,
True, # Do transfer ownership True, # Do transfer ownership
True) # F-contiguous is True: data is Fortran-cont. True) # F-contiguous is True: data is Fortran-cont.
rval = callback(npy_input, rval = callback(npy_input,
None, None,
nFramesPerBlock, nFramesPerBlock,
@ -118,9 +131,6 @@ cdef void audioCallbackPythonThreadFunction(void* voidsd) nogil:
fprintf(stderr, 'Exiting python thread...\n') fprintf(stderr, 'Exiting python thread...\n')
cdef class Daq: cdef class Daq:
cdef:
PyStreamData *sd
cppDaq* daq_device
def __cinit__(self): def __cinit__(self):
@ -132,9 +142,9 @@ cdef class Daq:
self.sd = NULL self.sd = NULL
def __dealloc__(self): def __dealloc__(self):
fprintf(stderr, "UlDaq.__dealloc__\n") # fprintf(stderr, "UlDaq.__dealloc__\n")
if self.sd is not NULL: if self.sd is not NULL:
fprintf(stderr, "UlDaq.__dealloc__, stopping stream.\n") fprintf(stderr, "UlDaq.__dealloc__: stopping stream.\n")
self.stop() self.stop()
def isRunning(self): def isRunning(self):
@ -144,143 +154,101 @@ cdef class Daq:
def getDeviceInfo(): def getDeviceInfo():
cdef: cdef:
vector[cppDeviceInfo] devinfos = cppDaq.getDeviceInfo() vector[cppDeviceInfo] devinfos = cppDaq.getDeviceInfo()
pydevinfo = [] pydevinfo = {}
for i in range(devinfos.size()): for i in range(devinfos.size()):
d = DeviceInfo() d = DeviceInfo()
d.devinfo = <cppDeviceInfo> devinfos[i] 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 return pydevinfo
# @cython.nonecheck(True) @cython.nonecheck(True)
# def start(self, avstream): def start(self, avstream):
# """ """
# Opens a stream with specified parameters Opens a stream with specified parameters
# Args: Args:
# avstream: AvStream instance 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: if self.sd is not NULL:
# assert self.daq_device is not NULL assert self.daq_device is not NULL
# raise RuntimeError('Stream is already opened.') raise RuntimeError('Stream is already opened.')
# daqconfig = avstream.daqconfig pydaqconfig = avstream.daqconfig
# avtype = avstream.avtype avtype = avstream.avtype
# device = avstream.device pydevinfo = avstream.deviceinfo
# cdef: daqconfig = &(pydaqconfig.config)
# bint duplex_mode = daqconfig.duplex_mode devinfo = &(pydevinfo.devinfo)
# bint monitorOutput = daqconfig.monitor_gen
# unsigned int nFramesPerBlock = daqconfig.nFramesPerBlock self.daq_device = cppDaq.createDaq(devinfo[0], daqconfig[0])
# unsigned int samplerate cdef:
cppDaq* daq = self.daq_device
# int i cdef:
# bint in_stream=False unsigned int nFramesPerBlock = daq.framesPerBlock()
# bint out_stream=False double samplerate = daq.samplerate()
DataType dtype = daq.dataType()
# cppDeviceInfo devinfo if nFramesPerBlock > 8192 or nFramesPerBlock < 512:
# DaqConfiguration cppconfig del self.daq_device
raise ValueError('Invalid number of nFramesPerBlock')
# cppDaq* daq_device
# if daqconfig.nFramesPerBlock > 8192 or daqconfig.nFramesPerBlock < 512:
# raise ValueError('Invalid number of nFramesPerBlock')
# All set, allocate the stream!
# # if daqconfig.outputDelayBlocks != 0: self.sd = <PyStreamData*> malloc(sizeof(PyStreamData))
# # print('WARNING: OutputDelayBlocks not supported by API') if self.sd == NULL:
del daq
# # Determine sample rate and sample format, determine whether we are an raise MemoryError('Could not allocate stream: memory error.')
# # 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.stopThread.store(False)
# self.sd = <PyStreamData*> malloc(sizeof(PyStreamData)) self.sd.inQueue = NULL
# if self.sd == NULL: self.sd.outQueue = NULL
# raise MemoryError('Could not allocate stream: memory error.')
self.sd.thread = NULL
self.sd.samplerate = <double> samplerate
# self.sd.stopThread.store(False) self.sd.ninchannels = daq.neninchannels()
# self.sd.inQueue = NULL self.sd.noutchannels = daq.nenoutchannels()
# self.sd.outQueue = NULL self.sd.nBytesPerChan = nFramesPerBlock*dtype.sw
self.sd.nFramesPerBlock = nFramesPerBlock
self.sd.npy_format = getNumpyDataType(dtype)
# self.sd.thread = NULL if daq.neninchannels() > 0:
# self.sd.samplerate = <double> samplerate self.sd.inQueue = new SafeQueue[void*]()
if daq.nenoutchannels() > 0:
self.sd.inQueue = new SafeQueue[void*]()
# self.sd.ninchannels = 0 self.sd.pyCallback = <PyObject*> avstream._audioCallback
# self.sd.noutchannels = 0
# self.sd.nBytesPerChan = daqconfig.nFramesPerBlock*sizeof(double)
# self.sd.nFramesPerBlock = daqconfig.nFramesPerBlock
# Increase reference count to the callback
Py_INCREF(<object> avstream._audioCallback)
# # Create channel maps for in channels, set in stream with nogil:
# # parameters self.sd.thread = new CPPThread[void*, void (*)(void*)](audioCallbackPythonThreadFunction,
# inch_enabled = 4*[False] <void*> self.sd)
# if in_stream:
# inch_enabled = [True if ch.channel_enabled else False for ch in
# daqconfig.getInputChannels()]
# self.sd.inQueue = new SafeQueue[void*]() # Allow stream stome time to start
CPPsleep_ms(500)
# # Create channel maps for output channels self.daq_device.start(
# outch_enabled = 1*[False] self.sd.inQueue,
# if out_stream: self.sd.outQueue)
# outch_enabled = [True if ch.channel_enabled else False for ch in
# daqconfig.getOutputChannels()]
# self.sd.outQueue = new SafeQueue[void*]() return nFramesPerBlock, self.daq_device.samplerate()
# 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()
def stop(self): def stop(self):
if self.sd is NULL: 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 dataclasses_json import dataclass_json
from ..lasp_common import lasp_shelve, SIQtys, Qty from ..lasp_common import lasp_shelve, SIQtys, Qty
import json
class CouplingMode: class CouplingMode:
@ -25,111 +26,126 @@ class Range:
tenV = '+/- 10 V' tenV = '+/- 10 V'
undefined = 'Undefined' undefined = 'Undefined'
@dataclass_json @dataclass_json
@dataclass @dataclass
class DAQChannel: class DaqChannel:
channel_enabled: bool channel_enabled: bool
channel_name: str = 'Unnamed channel' channel_name: str = 'Unnamed channel'
sensitivity: float = 1.0 sensitivity: float = 1.0
qty: Qty = SIQtys.default range_index: int = 0
range_: str = 'Undefined'
ACCoupling_enabled: bool = False ACCoupling_enabled: bool = False
IEPE_enabled: bool = False IEPE_enabled: bool = False
cdef class DaqConfiguration:
cdef class DAQConfiguration:
""" """
Initialize a device descriptor Initialize a device descriptor
""" """
# DaqConfiguration config
def getInputChannels(self): @staticmethod
return self.input_channel_configs def from_json(self, jsonstring):
config_dict = json.loads(jsonstring)
return DaqConfiguration.from_dict(config_dict)
def getOutputChannels(self): @staticmethod
return self.output_channel_configs 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): pydaqcfg = DaqConfiguration()
""" pydaqcfg.config = config
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
def lastEnabledInputChannelNumber(self): return pydaqcfg
last = -1
for i, ch in enumerate(self.input_channel_configs):
if ch.channel_enabled:
last = i
return last
def lastEnabledOutputChannelNumber(self): def to_json(self):
last = -1 return json.dumps(dict(
for i, ch in enumerate(self.output_channel_configs): apicode = self.api.apicode,
print(ch) device_name = self.config.device_name.decode('utf-8'),
if ch.channel_enabled:
last = i
return last
def getEnabledInputChannels(self): eninchannels = self.eninchannels(),
en_channels = [] enoutchannels = self.enoutchannels(),
for chan in self.input_channel_configs:
if chan.channel_enabled:
en_channels.append(chan)
return en_channels
def getEnabledInputChannelNames(self): inchannel_names = [name.decode('utf-8') for name in
return [ch.channel_name for ch in self.getEnabledInputChannels()] 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): inputIEPEEnabled = self.config.inputIEPEEnabled,
return [ch.qty for ch in self.getEnabledInputChannels()] inputACCouplingMode = self.config.inputACCouplingMode,
inputRangeIndices = self.config.inputRangeIndices,
))
def getEnabledInputChannelSensitivities(self): def getInChannel(self, i:int):
return [ return DaqChannel(
float(channel.sensitivity) channel_enabled=self.config.eninchannels[i],
for channel in self.getEnabledInputChannels() 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): def setOutChannel(self, i:int, daqchannel: DaqChannel):
en_channels = [] self.config.enoutchannels[i] = daqchannel.channel_enabled
for chan in self.output_channel_configs: self.config.outchannel_names[i] = daqchannel.channel_name
if chan.channel_enabled:
en_channels.append(chan)
return en_channels
def getEnabledOutputChannelQtys(self): def eninchannels(self):
return [ch.qty for ch in self.getEnabledOutputChannels()] return self.config.eninchannels
def getEnabledOutputChannelSensitivities(self): def enoutchannels(self):
return [ return self.config.enoutchannels
float(channel.sensitivity)
for channel in self.getEnabledOutputChannels()
]
@staticmethod @staticmethod
def loadConfigsJSON(): def loadConfigsJSON():
with lasp_shelve() as sh: with lasp_shelve() as sh:
configs_json = sh.load('daqconfigs', {}) configs_json = sh.load('daqconfigs', {})
return configs_json return configs_json
@staticmethod @staticmethod
def loadConfigs(): def loadConfigs():
""" """
Returns a list of currently available configurations Returns a list of currently available configurations
""" """
configs_json = DAQConfiguration.loadConfigsJSON() configs_json = DaqConfiguration.loadConfigsJSON()
configs = {} configs = {}
for name, val in configs_json.items(): for name, val in configs_json.items():
configs[name] = DAQConfiguration.from_json(val) configs[name] = DaqConfiguration.from_json(val)
return configs return configs
def saveConfig(self, name): def saveConfig(self, name):
configs_json = DAQConfiguration.loadConfigsJSON() configs_json = DaqConfiguration.loadConfigsJSON()
with lasp_shelve() as sh: with lasp_shelve() as sh:
configs_json[name] = self.to_json() configs_json[name] = self.to_json()
@ -138,6 +154,6 @@ cdef class DAQConfiguration:
@staticmethod @staticmethod
def deleteConfig(name): def deleteConfig(name):
with lasp_shelve() as sh: with lasp_shelve() as sh:
cur_configs = DAQConfiguration.loadConfigs() cur_configs = DaqConfiguration.loadConfigs()
del cur_configs[name] del cur_configs[name]
sh.store('daqconfigs', cur_configs) sh.store('daqconfigs', cur_configs)

View File

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