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
int api_specific_devindex
vector[DataType] availableDataTypes vector[DataType] availableDataTypes
int prefDataTypeIndex
vector[double] availableSampleRates vector[double] availableSampleRates
int prefSampleRateIndex
vector[us] availableFramesPerBlock vector[us] availableFramesPerBlock
unsigned prefFramesPerBlockIndex
int prefSampleRateIndex dvec availableInputRanges
int prefInputRangeIndex int prefInputRangeIndex
int prefFramesPerBlockIndex
unsigned ninchannels
unsigned noutchannels
bool hasInputIEPE
bool hasInputACCouplingSwitch
bool hasInputTrigger
vector[double] availableInputRanges
cdef cppclass DaqConfiguration: unsigned ninchannels
boolvec eninchannels unsigned noutchannels
boolvec enoutchannels
vector[string] channel_names
vector[double] channel_sensitivities
unsigned sampleRateIndex
DataType datatype
bool monitorOutput
unsigned nFramesPerBlock;
boolvec inputIEPEEnabled; bool hasInputIEPE
boolvec inputACCouplingMode; bool hasInputACCouplingSwitch
usvec inputRangeIndices; bool hasInputTrigger
cdef cppclass cppDaqConfiguration "DaqConfiguration":
DaqApi api
string device_name
boolvec eninchannels
boolvec enoutchannels
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": 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) {
@ -72,26 +75,272 @@ void fillRtAudioDeviceInfo(vector<DeviceInfo> &devinfolist) {
d.prefDataTypeIndex = d.availableDataTypes.size() - 1; d.prefDataTypeIndex = d.availableDataTypes.size() - 1;
devinfolist.push_back(d); devinfolist.push_back(d);
} }
} }
} }
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')
# # 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! # All set, allocate the stream!
# self.sd = <PyStreamData*> malloc(sizeof(PyStreamData)) self.sd = <PyStreamData*> malloc(sizeof(PyStreamData))
# if self.sd == NULL: if self.sd == NULL:
# raise MemoryError('Could not allocate stream: memory error.') del daq
raise MemoryError('Could not allocate stream: memory error.')
# self.sd.stopThread.store(False) self.sd.stopThread.store(False)
# self.sd.inQueue = NULL self.sd.inQueue = NULL
# self.sd.outQueue = NULL self.sd.outQueue = NULL
# self.sd.thread = NULL self.sd.thread = NULL
# self.sd.samplerate = <double> samplerate self.sd.samplerate = <double> samplerate
# self.sd.ninchannels = 0 self.sd.ninchannels = daq.neninchannels()
# self.sd.noutchannels = 0 self.sd.noutchannels = daq.nenoutchannels()
# self.sd.nBytesPerChan = daqconfig.nFramesPerBlock*sizeof(double) self.sd.nBytesPerChan = nFramesPerBlock*dtype.sw
# self.sd.nFramesPerBlock = daqconfig.nFramesPerBlock self.sd.nFramesPerBlock = nFramesPerBlock
self.sd.npy_format = getNumpyDataType(dtype)
if daq.neninchannels() > 0:
self.sd.inQueue = new SafeQueue[void*]()
if daq.nenoutchannels() > 0:
self.sd.inQueue = new SafeQueue[void*]()
# # Create channel maps for in channels, set in stream self.sd.pyCallback = <PyObject*> avstream._audioCallback
# # 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*]() # Increase reference count to the callback
Py_INCREF(<object> avstream._audioCallback)
# # Create channel maps for output channels with nogil:
# outch_enabled = 1*[False] self.sd.thread = new CPPThread[void*, void (*)(void*)](audioCallbackPythonThreadFunction,
# if out_stream: <void*> self.sd)
# outch_enabled = [True if ch.channel_enabled else False for ch in
# daqconfig.getOutputChannels()]
# self.sd.outQueue = new SafeQueue[void*]() # Allow stream stome time to start
CPPsleep_ms(500)
# daq_device = createDaqDevice(devinfo, cppconfig) self.daq_device.start(
self.sd.inQueue,
self.sd.outQueue)
# self.sd.pyCallback = <PyObject*> avstream._audioCallback return nFramesPerBlock, self.daq_device.samplerate()
# 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)