RtAudio in a theoretically working state. Now its time for some testing.
This commit is contained in:
parent
622dd1f319
commit
6b8abb878a
@ -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)
|
||||||
|
@ -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")
|
||||||
|
@ -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 +
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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:
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
include "lasp_common_decls.pxd"
|
||||||
|
|
||||||
|
cdef class DaqConfiguration:
|
||||||
|
cdef:
|
||||||
|
cppDaqConfiguration config
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user