Compare commits
No commits in common. "e9f500d460e2c07e307b5c63167bd9b5a0e2f658" and "08010e56dd57a703c5ba79a08ad5570128dce744" have entirely different histories.
e9f500d460
...
08010e56dd
@ -13,8 +13,8 @@ if(WIN32)
|
|||||||
set(DEFAULT_PORTAUDIO ON)
|
set(DEFAULT_PORTAUDIO ON)
|
||||||
set(DEFAULT_ULDAQ OFF)
|
set(DEFAULT_ULDAQ OFF)
|
||||||
else()
|
else()
|
||||||
set(DEFAULT_RTAUDIO OFF)
|
set(DEFAULT_RTAUDIO ON)
|
||||||
set(DEFAULT_PORTAUDIO ON)
|
set(DEFAULT_PORTAUDIO OFF)
|
||||||
set(DEFAULT_ULDAQ ON)
|
set(DEFAULT_ULDAQ ON)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
@ -3,10 +3,9 @@ if(LASP_HAS_PORTAUDIO)
|
|||||||
message("Building with Portaudio backend")
|
message("Building with Portaudio backend")
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
else()
|
else()
|
||||||
# Unix
|
|
||||||
set(PA_USE_ALSA TRUE CACHE BOOL "Build PortAudio with ALSA backend")
|
set(PA_USE_ALSA TRUE CACHE BOOL "Build PortAudio with ALSA backend")
|
||||||
set(PA_USE_JACK FALSE CACHE BOOL "Build PortAudio with Jack backend")
|
set(PA_USE_JACK TRUE CACHE BOOL "Build PortAudio with Jack backend")
|
||||||
set(PA_USE_PULSEAUDIO FALSE CACHE BOOL "Build PortAudio with PulseAudio backend")
|
# set(PA_ALSA_DYNAMIC FALSE CACHE BOOL "Build static library of ALSA")
|
||||||
set(PA_BUILD_SHARED_LIBS FALSE CACHE BOOL "Build static library")
|
set(PA_BUILD_SHARED_LIBS FALSE CACHE BOOL "Build static library")
|
||||||
endif()
|
endif()
|
||||||
add_subdirectory(third_party/portaudio)
|
add_subdirectory(third_party/portaudio)
|
||||||
|
@ -38,18 +38,16 @@ pybind11_add_module(lasp_cpp MODULE lasp_cpp.cpp
|
|||||||
target_link_libraries(lasp_cpp PRIVATE lasp_device_lib lasp_dsp_lib
|
target_link_libraries(lasp_cpp PRIVATE lasp_device_lib lasp_dsp_lib
|
||||||
${OpenMP_CXX_LIBRARIES} ${LASP_FFT_LIBS} ${TARGET_OS_LINKLIBS})
|
${OpenMP_CXX_LIBRARIES} ${LASP_FFT_LIBS} ${TARGET_OS_LINKLIBS})
|
||||||
|
|
||||||
if(DEFINED PY_BUILD_CMAKE_MODULE_NAME)
|
# Install the Python module
|
||||||
# Install the Python module
|
install(TARGETS lasp_cpp
|
||||||
install(TARGETS lasp_cpp
|
EXCLUDE_FROM_ALL
|
||||||
|
COMPONENT python_modules
|
||||||
|
DESTINATION ${PY_BUILD_CMAKE_MODULE_NAME})
|
||||||
|
# Install the debug file for the Python module (Windows only)
|
||||||
|
if (WIN32)
|
||||||
|
install(FILES $<TARGET_PDB_FILE:_add_module>
|
||||||
EXCLUDE_FROM_ALL
|
EXCLUDE_FROM_ALL
|
||||||
COMPONENT python_modules
|
COMPONENT python_modules
|
||||||
DESTINATION ${PY_BUILD_CMAKE_MODULE_NAME})
|
DESTINATION ${PY_BUILD_CMAKE_MODULE_NAME}
|
||||||
# Install the debug file for the Python module (Windows only)
|
OPTIONAL)
|
||||||
if (WIN32)
|
endif()
|
||||||
install(FILES $<TARGET_PDB_FILE:_add_module>
|
|
||||||
EXCLUDE_FROM_ALL
|
|
||||||
COMPONENT python_modules
|
|
||||||
DESTINATION ${PY_BUILD_CMAKE_MODULE_NAME}
|
|
||||||
OPTIONAL)
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
@ -56,29 +56,13 @@ Daq::Daq(const DeviceInfo &devinfo, const DaqConfiguration &config)
|
|||||||
if (!config.match(devinfo)) {
|
if (!config.match(devinfo)) {
|
||||||
throw rte("DaqConfiguration does not match device info");
|
throw rte("DaqConfiguration does not match device info");
|
||||||
}
|
}
|
||||||
|
if (neninchannels(false) > devinfo.ninchannels) {
|
||||||
{
|
throw rte(
|
||||||
const int hich = getHighestEnabledInChannel();
|
"Number of enabled input channels is higher than device capability");
|
||||||
if (hich + 1 > devinfo.ninchannels)
|
|
||||||
{
|
|
||||||
throw rte(
|
|
||||||
string("Highest of enabled input channel: ") +
|
|
||||||
to_string(hich) +
|
|
||||||
string(" is higher than device capability, which is: ") +
|
|
||||||
to_string(ninchannels) + ".");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (nenoutchannels() > devinfo.noutchannels) {
|
||||||
{
|
throw rte(
|
||||||
const int hoch = getHighestEnabledOutChannel();
|
"Number of enabled output channels is higher than device capability");
|
||||||
if (hoch + 1 > devinfo.noutchannels)
|
|
||||||
{
|
|
||||||
throw rte(
|
|
||||||
string("Highest of enabled output channel: ") +
|
|
||||||
to_string(hoch) +
|
|
||||||
string(" is higher than device capability, which is: ") +
|
|
||||||
to_string(noutchannels) + ".");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,10 +48,6 @@ public:
|
|||||||
logicError,
|
logicError,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Below the only members of this class, which are public.
|
|
||||||
bool isRunning = false;
|
|
||||||
StreamError errorType{StreamError::noError};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Map between error types and messages
|
* @brief Map between error types and messages
|
||||||
*/
|
*/
|
||||||
@ -65,7 +61,7 @@ public:
|
|||||||
{StreamError::logicError, "Logic error (probably a bug)"},
|
{StreamError::logicError, "Logic error (probably a bug)"},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bool isRunning = false;
|
||||||
/**
|
/**
|
||||||
* @brief Check if stream has error
|
* @brief Check if stream has error
|
||||||
*
|
*
|
||||||
@ -73,6 +69,8 @@ public:
|
|||||||
*/
|
*/
|
||||||
bool error() const { return errorType != StreamError::noError; };
|
bool error() const { return errorType != StreamError::noError; };
|
||||||
|
|
||||||
|
StreamError errorType{StreamError::noError};
|
||||||
|
|
||||||
std::string errorMsg() const { return errorMessages.at(errorType); }
|
std::string errorMsg() const { return errorMessages.at(errorType); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -56,12 +56,10 @@ DaqConfiguration::DaqConfiguration(const DeviceInfo &device) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool DaqConfiguration::match(const DeviceInfo &dev) const {
|
bool DaqConfiguration::match(const DeviceInfo &dev) const {
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
return (dev.device_name == device_name && dev.api == api);
|
return (dev.device_name == device_name && dev.api == api);
|
||||||
}
|
}
|
||||||
|
|
||||||
int DaqConfiguration::getHighestEnabledInChannel() const {
|
int DaqConfiguration::getHighestEnabledInChannel() const {
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
for (int i = inchannel_config.size() - 1; i > -1; i--) {
|
for (int i = inchannel_config.size() - 1; i > -1; i--) {
|
||||||
if (inchannel_config.at(i).enabled)
|
if (inchannel_config.at(i).enabled)
|
||||||
return i;
|
return i;
|
||||||
@ -70,15 +68,13 @@ int DaqConfiguration::getHighestEnabledInChannel() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int DaqConfiguration::getHighestEnabledOutChannel() const {
|
int DaqConfiguration::getHighestEnabledOutChannel() const {
|
||||||
DEBUGTRACE_ENTER;
|
for (us i = outchannel_config.size() - 1; i >= 0; i--) {
|
||||||
for (int i = outchannel_config.size() - 1; i > -1; i--) {
|
|
||||||
if (outchannel_config.at(i).enabled)
|
if (outchannel_config.at(i).enabled)
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
int DaqConfiguration::getLowestEnabledInChannel() const {
|
int DaqConfiguration::getLowestEnabledInChannel() const {
|
||||||
DEBUGTRACE_ENTER;
|
|
||||||
for (us i = 0; i < inchannel_config.size(); i++) {
|
for (us i = 0; i < inchannel_config.size(); i++) {
|
||||||
if (inchannel_config.at(i).enabled)
|
if (inchannel_config.at(i).enabled)
|
||||||
return i;
|
return i;
|
||||||
|
@ -148,14 +148,7 @@ const DaqApi rtaudioAsioApi("RtAudio Windows ASIO", LASP_RTAUDIO_APICODE,
|
|||||||
#endif
|
#endif
|
||||||
#if LASP_HAS_PORTAUDIO == 1
|
#if LASP_HAS_PORTAUDIO == 1
|
||||||
const us LASP_PORTAUDIO_APICODE = 2;
|
const us LASP_PORTAUDIO_APICODE = 2;
|
||||||
const DaqApi portaudioALSAApi("PortAudio Linux ALSA", LASP_PORTAUDIO_APICODE, 0);
|
const DaqApi portaudioApi("PortAudio Linux ALSA", LASP_PORTAUDIO_APICODE, 0);
|
||||||
const DaqApi portaudioPulseApi("PortAudio Linux PulseAudio", LASP_PORTAUDIO_APICODE, 1);
|
|
||||||
const DaqApi portaudioASIOApi("PortAudio Windows ASIO", LASP_PORTAUDIO_APICODE, 2);
|
|
||||||
const DaqApi portaudioDSApi("PortAudio Windows DirectSound", LASP_PORTAUDIO_APICODE, 3);
|
|
||||||
const DaqApi portaudioWMMEApi("PortAudio Windows WMME", LASP_PORTAUDIO_APICODE, 4);
|
|
||||||
const DaqApi portaudioWASAPIApi("PortAudio Windows WASAPI", LASP_PORTAUDIO_APICODE, 5);
|
|
||||||
const DaqApi portaudioWDMKS("PortAudio Windows WDMKS", LASP_PORTAUDIO_APICODE, 6);
|
|
||||||
const DaqApi portaudioDirectSoundApi("PortAudio Windows DirectSound", LASP_PORTAUDIO_APICODE, 7);
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class DeviceInfo;
|
class DeviceInfo;
|
||||||
|
@ -1,18 +1,15 @@
|
|||||||
/* #define DEBUGTRACE_ENABLED */
|
/* #define DEBUGTRACE_ENABLED */
|
||||||
#include "lasp_streammgr.h"
|
#include "lasp_streammgr.h"
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <functional>
|
|
||||||
#include <iostream>
|
|
||||||
#include <memory>
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
#include "debugtrace.hpp"
|
#include "debugtrace.hpp"
|
||||||
#include "lasp_biquadbank.h"
|
#include "lasp_biquadbank.h"
|
||||||
#include "lasp_indatahandler.h"
|
#include "lasp_indatahandler.h"
|
||||||
#include "lasp_thread.h"
|
#include "lasp_thread.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
using std::cerr;
|
using std::cerr;
|
||||||
using std::endl;
|
using std::endl;
|
||||||
@ -112,6 +109,7 @@ void StreamMgr::rescanDAQDevices_impl(std::function<void()> callback) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
void StreamMgr::inCallback(const DaqData &data) {
|
void StreamMgr::inCallback(const DaqData &data) {
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
|
|
||||||
std::scoped_lock<std::mutex> lck(_inDataHandler_mtx);
|
std::scoped_lock<std::mutex> lck(_inDataHandler_mtx);
|
||||||
@ -120,6 +118,7 @@ void StreamMgr::inCallback(const DaqData &data) {
|
|||||||
|
|
||||||
if (std::count_if(_inputFilters.cbegin(), _inputFilters.cend(),
|
if (std::count_if(_inputFilters.cbegin(), _inputFilters.cend(),
|
||||||
[](const auto &a) { return bool(a); }) > 0) {
|
[](const auto &a) { return bool(a); }) > 0) {
|
||||||
|
|
||||||
/// Found a filter in vector of input filters. So we have to apply the
|
/// Found a filter in vector of input filters. So we have to apply the
|
||||||
/// filters to each channel.
|
/// filters to each channel.
|
||||||
|
|
||||||
@ -152,6 +151,7 @@ void StreamMgr::inCallback(const DaqData &data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void StreamMgr::setSiggen(std::shared_ptr<Siggen> siggen) {
|
void StreamMgr::setSiggen(std::shared_ptr<Siggen> siggen) {
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
checkRightThread();
|
checkRightThread();
|
||||||
|
|
||||||
@ -179,8 +179,7 @@ void StreamMgr::setSiggen(std::shared_ptr<Siggen> siggen) {
|
|||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
template <typename T>
|
template <typename T> bool fillData(DaqData &data, const vd &signal) {
|
||||||
bool fillData(DaqData &data, const vd &signal) {
|
|
||||||
/* DEBUGTRACE_ENTER; */
|
/* DEBUGTRACE_ENTER; */
|
||||||
assert(data.nframes == signal.size());
|
assert(data.nframes == signal.size());
|
||||||
|
|
||||||
@ -213,6 +212,7 @@ bool fillData(DaqData &data, const vd &signal) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
void StreamMgr::outCallback(DaqData &data) {
|
void StreamMgr::outCallback(DaqData &data) {
|
||||||
|
|
||||||
/* DEBUGTRACE_ENTER; */
|
/* DEBUGTRACE_ENTER; */
|
||||||
|
|
||||||
std::scoped_lock<std::mutex> lck(_siggen_mtx);
|
std::scoped_lock<std::mutex> lck(_siggen_mtx);
|
||||||
@ -220,21 +220,21 @@ void StreamMgr::outCallback(DaqData &data) {
|
|||||||
if (_siggen) {
|
if (_siggen) {
|
||||||
vd signal = _siggen->genSignal(data.nframes);
|
vd signal = _siggen->genSignal(data.nframes);
|
||||||
switch (data.dtype) {
|
switch (data.dtype) {
|
||||||
case (DataTypeDescriptor::DataType::dtype_fl32):
|
case (DataTypeDescriptor::DataType::dtype_fl32):
|
||||||
fillData<float>(data, signal);
|
fillData<float>(data, signal);
|
||||||
break;
|
break;
|
||||||
case (DataTypeDescriptor::DataType::dtype_fl64):
|
case (DataTypeDescriptor::DataType::dtype_fl64):
|
||||||
fillData<double>(data, signal);
|
fillData<double>(data, signal);
|
||||||
break;
|
break;
|
||||||
case (DataTypeDescriptor::DataType::dtype_int8):
|
case (DataTypeDescriptor::DataType::dtype_int8):
|
||||||
fillData<int8_t>(data, signal);
|
fillData<int8_t>(data, signal);
|
||||||
break;
|
break;
|
||||||
case (DataTypeDescriptor::DataType::dtype_int16):
|
case (DataTypeDescriptor::DataType::dtype_int16):
|
||||||
fillData<int16_t>(data, signal);
|
fillData<int16_t>(data, signal);
|
||||||
break;
|
break;
|
||||||
case (DataTypeDescriptor::DataType::dtype_int32):
|
case (DataTypeDescriptor::DataType::dtype_int32):
|
||||||
fillData<int32_t>(data, signal);
|
fillData<int32_t>(data, signal);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Set all values to 0.
|
// Set all values to 0.
|
||||||
@ -257,6 +257,8 @@ StreamMgr::~StreamMgr() {
|
|||||||
// virtual methods. This was really a bug.
|
// virtual methods. This was really a bug.
|
||||||
_inputStream.reset();
|
_inputStream.reset();
|
||||||
_outputStream.reset();
|
_outputStream.reset();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
void StreamMgr::stopAllStreams() {
|
void StreamMgr::stopAllStreams() {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
@ -271,11 +273,11 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
|
|||||||
|
|
||||||
bool isInput = std::count_if(config.inchannel_config.cbegin(),
|
bool isInput = std::count_if(config.inchannel_config.cbegin(),
|
||||||
config.inchannel_config.cend(),
|
config.inchannel_config.cend(),
|
||||||
[](auto &i) { return i.enabled; }) > 0;
|
[](auto &i) { return i.enabled; });
|
||||||
|
|
||||||
bool isOutput = std::count_if(config.outchannel_config.cbegin(),
|
bool isOutput = std::count_if(config.outchannel_config.cbegin(),
|
||||||
config.outchannel_config.cend(),
|
config.outchannel_config.cend(),
|
||||||
[](auto &i) { return i.enabled; }) > 0;
|
[](auto &i) { return i.enabled; });
|
||||||
|
|
||||||
// Find the first device that matches with the configuration
|
// Find the first device that matches with the configuration
|
||||||
std::scoped_lock lck(_devices_mtx);
|
std::scoped_lock lck(_devices_mtx);
|
||||||
@ -300,40 +302,34 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
|
|||||||
bool isDuplex = isInput && isOutput;
|
bool isDuplex = isInput && isOutput;
|
||||||
|
|
||||||
if (!isInput && !isOutput) {
|
if (!isInput && !isOutput) {
|
||||||
throw rte(
|
throw rte("Neither input, nor output channels enabled for "
|
||||||
"Attempted stream start failed, stream does not have any enabled "
|
"stream. Cannot start.");
|
||||||
"channels. Please first enable channels in the channel configuration.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isInput && _inputStream) {
|
if (isInput && _inputStream) {
|
||||||
throw rte(
|
throw rte("Error: an input stream is already running. Please "
|
||||||
"Error: an input stream is already running. Please "
|
"first stop existing stream");
|
||||||
"first stop existing stream");
|
|
||||||
} else if (isOutput && _outputStream) {
|
} else if (isOutput && _outputStream) {
|
||||||
throw rte(
|
throw rte("Error: output stream is already running. Please "
|
||||||
"Error: output stream is already running. Please "
|
"first stop existing stream");
|
||||||
"first stop existing stream");
|
|
||||||
} else if (_inputStream) {
|
} else if (_inputStream) {
|
||||||
if (_inputStream->duplexMode() && isOutput) {
|
if (_inputStream->duplexMode() && isOutput) {
|
||||||
throw rte(
|
throw rte("Error: output stream is already running (in duplex mode). "
|
||||||
"Error: output stream is already running (in duplex mode). "
|
"Please "
|
||||||
"Please "
|
"first stop existing stream");
|
||||||
"first stop existing stream");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_outputStream && isInput && _outputStream->duplexModeForced &&
|
if (_outputStream && isInput && _outputStream->duplexModeForced &&
|
||||||
config.match(*_outputStream)) {
|
config.match(*_outputStream)) {
|
||||||
throw rte(
|
throw rte("This device is already opened for output. If input is also "
|
||||||
"This device is already opened for output. If input is also "
|
"required, please enable duplex mode for this device");
|
||||||
"required, please enable duplex mode for this device");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_inputStream && isOutput && _inputStream->duplexModeForced &&
|
if (_inputStream && isOutput && _inputStream->duplexModeForced &&
|
||||||
config.match(*_inputStream)) {
|
config.match(*_inputStream)) {
|
||||||
throw rte(
|
throw rte("This device is already opened for input. If output is also "
|
||||||
"This device is already opened for input. If output is also "
|
"required, please enable duplex mode for this device");
|
||||||
"required, please enable duplex mode for this device");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
InDaqCallback inCallback;
|
InDaqCallback inCallback;
|
||||||
@ -356,13 +352,13 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
|
|||||||
d fs = daq->samplerate();
|
d fs = daq->samplerate();
|
||||||
/// Create input filters
|
/// Create input filters
|
||||||
_inputFilters.clear();
|
_inputFilters.clear();
|
||||||
|
|
||||||
/// No input filter for monitor channel, which comes as the first input
|
/// No input filter for monitor channel, which comes as the first input channel
|
||||||
/// channel In the list
|
/// In the list
|
||||||
if (config.monitorOutput && devinfo->hasInternalOutputMonitor) {
|
if (config.monitorOutput && devinfo->hasInternalOutputMonitor) {
|
||||||
_inputFilters.push_back(nullptr);
|
_inputFilters.push_back(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto &ch : daq->inchannel_config) {
|
for (auto &ch : daq->inchannel_config) {
|
||||||
if (ch.enabled) {
|
if (ch.enabled) {
|
||||||
if (ch.digitalHighPassCutOn < 0) {
|
if (ch.digitalHighPassCutOn < 0) {
|
||||||
@ -375,7 +371,7 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
|
|||||||
SeriesBiquad::firstOrderHighPass(fs, ch.digitalHighPassCutOn)));
|
SeriesBiquad::firstOrderHighPass(fs, ch.digitalHighPassCutOn)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // End of input filter creation
|
} // End of input filter creation
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isOutput) {
|
if (isOutput) {
|
||||||
@ -402,6 +398,7 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
void StreamMgr::stopStream(const StreamType t) {
|
void StreamMgr::stopStream(const StreamType t) {
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
checkRightThread();
|
checkRightThread();
|
||||||
|
|
||||||
@ -426,7 +423,7 @@ void StreamMgr::stopStream(const StreamType t) {
|
|||||||
throw rte("Output stream is not running");
|
throw rte("Output stream is not running");
|
||||||
}
|
}
|
||||||
_outputStream.reset();
|
_outputStream.reset();
|
||||||
} // end else
|
} // end else
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -439,9 +436,8 @@ void StreamMgr::addInDataHandler(InDataHandler *handler) {
|
|||||||
|
|
||||||
if (std::find(_inDataHandlers.cbegin(), _inDataHandlers.cend(), handler) !=
|
if (std::find(_inDataHandlers.cbegin(), _inDataHandlers.cend(), handler) !=
|
||||||
_inDataHandlers.cend()) {
|
_inDataHandlers.cend()) {
|
||||||
throw std::runtime_error(
|
throw std::runtime_error("Error: handler already added. Probably start() "
|
||||||
"Error: handler already added. Probably start() "
|
"is called more than once on a handler object");
|
||||||
"is called more than once on a handler object");
|
|
||||||
}
|
}
|
||||||
_inDataHandlers.push_back(handler);
|
_inDataHandlers.push_back(handler);
|
||||||
DEBUGTRACE_PRINT(_inDataHandlers.size());
|
DEBUGTRACE_PRINT(_inDataHandlers.size());
|
||||||
@ -471,6 +467,7 @@ Daq::StreamStatus StreamMgr::getStreamStatus(const StreamType type) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Daq *StreamMgr::getDaq(StreamType type) const {
|
const Daq *StreamMgr::getDaq(StreamType type) const {
|
||||||
|
|
||||||
checkRightThread();
|
checkRightThread();
|
||||||
|
|
||||||
if (type == StreamType::input) {
|
if (type == StreamType::input) {
|
||||||
|
@ -3,22 +3,23 @@
|
|||||||
#include "lasp_config.h"
|
#include "lasp_config.h"
|
||||||
|
|
||||||
#if LASP_HAS_PORTAUDIO == 1
|
#if LASP_HAS_PORTAUDIO == 1
|
||||||
|
#include "lasp_portaudiodaq.h"
|
||||||
|
#include "portaudio.h"
|
||||||
#include <gsl-lite/gsl-lite.hpp>
|
#include <gsl-lite/gsl-lite.hpp>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "lasp_portaudiodaq.h"
|
|
||||||
#include "portaudio.h"
|
|
||||||
|
|
||||||
using rte = std::runtime_error;
|
using rte = std::runtime_error;
|
||||||
using std::cerr;
|
using std::cerr;
|
||||||
using std::endl;
|
using std::endl;
|
||||||
using std::string;
|
using std::string;
|
||||||
using std::to_string;
|
using std::to_string;
|
||||||
|
|
||||||
inline void throwIfError(PaError e) {
|
inline void throwIfError(PaError e)
|
||||||
|
{
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
if (e != paNoError) {
|
if (e != paNoError)
|
||||||
|
{
|
||||||
throw rte(string("PortAudio backend error: ") + Pa_GetErrorText(e));
|
throw rte(string("PortAudio backend error: ") + Pa_GetErrorText(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -26,102 +27,69 @@ inline void throwIfError(PaError e) {
|
|||||||
/**
|
/**
|
||||||
* @brief Device info, plus PortAudio stuff
|
* @brief Device info, plus PortAudio stuff
|
||||||
*/
|
*/
|
||||||
class OurPaDeviceInfo : public DeviceInfo {
|
class OurPaDeviceInfo : public DeviceInfo
|
||||||
public:
|
{
|
||||||
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief Store instance to PaDeviceInfo.
|
* @brief Store instance to PaDeviceInfo.
|
||||||
*/
|
*/
|
||||||
PaDeviceInfo _paDevInfo;
|
PaDeviceInfo _paDevInfo;
|
||||||
|
|
||||||
virtual std::unique_ptr<DeviceInfo> clone() const override final {
|
virtual std::unique_ptr<DeviceInfo> clone() const override final
|
||||||
|
{
|
||||||
return std::make_unique<OurPaDeviceInfo>(*this);
|
return std::make_unique<OurPaDeviceInfo>(*this);
|
||||||
}
|
}
|
||||||
OurPaDeviceInfo &operator=(const OurPaDeviceInfo &) = delete;
|
|
||||||
OurPaDeviceInfo(const OurPaDeviceInfo &) = default;
|
|
||||||
OurPaDeviceInfo(const PaDeviceInfo &o) : DeviceInfo(), _paDevInfo(o) {}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) {
|
void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist)
|
||||||
|
{
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
bool shouldPaTerminate = false;
|
try
|
||||||
try {
|
{
|
||||||
|
|
||||||
PaError err = Pa_Initialize();
|
PaError err = Pa_Initialize();
|
||||||
/// PortAudio says that Pa_Terminate() should not be called whenever there
|
/// PortAudio says that Pa_Terminate() should not be called whenever there
|
||||||
/// is an error in Pa_Initialize(). This is opposite to what most examples
|
/// is an error in Pa_Initialize(). This is opposite to what most examples
|
||||||
/// of PortAudio show.
|
/// of PortAudio show.
|
||||||
throwIfError(err);
|
throwIfError(err);
|
||||||
shouldPaTerminate = true;
|
|
||||||
|
|
||||||
auto fin = gsl::finally([&err] {
|
auto fin = gsl::finally([&err]
|
||||||
|
{
|
||||||
DEBUGTRACE_PRINT("Terminating PortAudio instance");
|
DEBUGTRACE_PRINT("Terminating PortAudio instance");
|
||||||
err = Pa_Terminate();
|
err = Pa_Terminate();
|
||||||
if (err != paNoError) {
|
if (err != paNoError) {
|
||||||
cerr << "Error terminating PortAudio. Do not know what to do." << endl;
|
cerr << "Error terminating PortAudio. Do not know what to do." << endl;
|
||||||
}
|
} });
|
||||||
});
|
|
||||||
|
|
||||||
const PaHostApiIndex apicount = Pa_GetHostApiCount();
|
|
||||||
if (apicount < 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
/* const PaDeviceInfo *deviceInfo; */
|
/* const PaDeviceInfo *deviceInfo; */
|
||||||
const int numDevices = Pa_GetDeviceCount();
|
const int numDevices = Pa_GetDeviceCount();
|
||||||
if (numDevices < 0) {
|
if (numDevices < 0)
|
||||||
|
{
|
||||||
throw rte("PortAudio could not find any devices");
|
throw rte("PortAudio could not find any devices");
|
||||||
}
|
}
|
||||||
for (us i = 0; i < (us)numDevices; i++) {
|
for (us i = 0; i < (us)numDevices; i++)
|
||||||
|
{
|
||||||
/* DEBUGTRACE_PRINT(i); */
|
/* DEBUGTRACE_PRINT(i); */
|
||||||
bool hasDuplexMode = false;
|
|
||||||
|
|
||||||
const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(i);
|
const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(i);
|
||||||
if (!deviceInfo) {
|
if (!deviceInfo)
|
||||||
|
{
|
||||||
throw rte("No device info struct returned");
|
throw rte("No device info struct returned");
|
||||||
}
|
}
|
||||||
OurPaDeviceInfo d(*deviceInfo);
|
OurPaDeviceInfo d;
|
||||||
|
d._paDevInfo = *deviceInfo;
|
||||||
// We store the name in d.device_name
|
// We store the name in d.device_name
|
||||||
d._paDevInfo.name = nullptr;
|
d._paDevInfo.name = nullptr;
|
||||||
|
d.api = portaudioApi;
|
||||||
d.device_name = deviceInfo->name;
|
d.device_name = deviceInfo->name;
|
||||||
|
|
||||||
const PaHostApiInfo *hostapiinfo = Pa_GetHostApiInfo(deviceInfo->hostApi);
|
|
||||||
if (hostapiinfo == nullptr) {
|
|
||||||
throw std::runtime_error("Hostapi nullptr!");
|
|
||||||
}
|
|
||||||
switch (hostapiinfo->type) {
|
|
||||||
case paALSA:
|
|
||||||
// Duplex mode for alsa
|
|
||||||
hasDuplexMode = true;
|
|
||||||
d.api = portaudioALSAApi;
|
|
||||||
break;
|
|
||||||
case paASIO:
|
|
||||||
d.api = portaudioASIOApi;
|
|
||||||
break;
|
|
||||||
case paDirectSound:
|
|
||||||
d.api = portaudioDirectSoundApi;
|
|
||||||
break;
|
|
||||||
case paMME:
|
|
||||||
d.api = portaudioWMMEApi;
|
|
||||||
break;
|
|
||||||
case paWDMKS:
|
|
||||||
d.api = portaudioWDMKS;
|
|
||||||
break;
|
|
||||||
case paWASAPI:
|
|
||||||
d.api = portaudioWASAPIApi;
|
|
||||||
break;
|
|
||||||
case paPulseAudio:
|
|
||||||
d.api = portaudioPulseApi;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw rte("Unimplemented portaudio API!");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
d.availableDataTypes = {DataTypeDescriptor::DataType::dtype_int16,
|
d.availableDataTypes = {DataTypeDescriptor::DataType::dtype_int16,
|
||||||
DataTypeDescriptor::DataType::dtype_int32,
|
DataTypeDescriptor::DataType::dtype_int32,
|
||||||
DataTypeDescriptor::DataType::dtype_fl32};
|
DataTypeDescriptor::DataType::dtype_fl32};
|
||||||
|
|
||||||
d.prefDataTypeIndex = 2;
|
d.prefDataTypeIndex = 2;
|
||||||
|
|
||||||
d.availableSampleRates = {8000.0, 9600.0, 11025.0, 12000.0, 16000.0,
|
d.availableSampleRates = {8000.0, 9600.0, 11025.0, 12000.0, 16000.0,
|
||||||
22050.0, 24000.0, 32000.0, 44100.0, 48000.0,
|
22050.0, 24000.0, 32000.0, 44100.0, 48000.0,
|
||||||
88200.0, 96000.0, 192000.0};
|
88200.0, 96000.0, 192000.0};
|
||||||
d.prefSampleRateIndex = 9;
|
d.prefSampleRateIndex = 9;
|
||||||
@ -137,20 +105,12 @@ void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) {
|
|||||||
d.ninchannels = deviceInfo->maxInputChannels;
|
d.ninchannels = deviceInfo->maxInputChannels;
|
||||||
d.noutchannels = deviceInfo->maxOutputChannels;
|
d.noutchannels = deviceInfo->maxOutputChannels;
|
||||||
|
|
||||||
// Duplex mode, only for ALSA devices
|
|
||||||
d.hasDuplexMode = hasDuplexMode;
|
|
||||||
|
|
||||||
devinfolist.push_back(std::make_unique<OurPaDeviceInfo>(d));
|
devinfolist.push_back(std::make_unique<OurPaDeviceInfo>(d));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (rte &e) {
|
catch (rte &e)
|
||||||
if (shouldPaTerminate) {
|
{
|
||||||
PaError err = Pa_Terminate();
|
|
||||||
if (err != paNoError) {
|
|
||||||
cerr << "Error terminating PortAudio. Do not know what to do." << endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cerr << "PortAudio backend error: " << e.what() << std::endl;
|
cerr << "PortAudio backend error: " << e.what() << std::endl;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -175,14 +135,16 @@ static int rawPaCallback(const void *inputBuffer, void *outputBuffer,
|
|||||||
const PaStreamCallbackTimeInfo *timeInfo,
|
const PaStreamCallbackTimeInfo *timeInfo,
|
||||||
PaStreamCallbackFlags statusFlags, void *userData);
|
PaStreamCallbackFlags statusFlags, void *userData);
|
||||||
|
|
||||||
class PortAudioDaq : public Daq {
|
class PortAudioDaq : public Daq
|
||||||
|
{
|
||||||
|
bool _shouldPaTerminate = false;
|
||||||
PaStream *_stream = nullptr;
|
PaStream *_stream = nullptr;
|
||||||
std::atomic<StreamStatus::StreamError> _streamError =
|
std::atomic<StreamStatus::StreamError> _streamError =
|
||||||
StreamStatus::StreamError::noError;
|
StreamStatus::StreamError::noError;
|
||||||
InDaqCallback _incallback;
|
InDaqCallback _incallback;
|
||||||
OutDaqCallback _outcallback;
|
OutDaqCallback _outcallback;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PortAudioDaq(const OurPaDeviceInfo &devinfo_gen,
|
PortAudioDaq(const OurPaDeviceInfo &devinfo_gen,
|
||||||
const DaqConfiguration &config);
|
const DaqConfiguration &config);
|
||||||
|
|
||||||
@ -211,11 +173,13 @@ class PortAudioDaq : public Daq {
|
|||||||
};
|
};
|
||||||
|
|
||||||
std::unique_ptr<Daq> createPortAudioDevice(const DeviceInfo &devinfo,
|
std::unique_ptr<Daq> createPortAudioDevice(const DeviceInfo &devinfo,
|
||||||
const DaqConfiguration &config) {
|
const DaqConfiguration &config)
|
||||||
DEBUGTRACE_ENTER;
|
{
|
||||||
|
|
||||||
const OurPaDeviceInfo *_info =
|
const OurPaDeviceInfo *_info =
|
||||||
dynamic_cast<const OurPaDeviceInfo *>(&devinfo);
|
dynamic_cast<const OurPaDeviceInfo *>(&devinfo);
|
||||||
if (_info == nullptr) {
|
if (_info == nullptr)
|
||||||
|
{
|
||||||
throw rte("BUG: Could not cast DeviceInfo to OurPaDeviceInfo");
|
throw rte("BUG: Could not cast DeviceInfo to OurPaDeviceInfo");
|
||||||
}
|
}
|
||||||
return std::make_unique<PortAudioDaq>(*_info, config);
|
return std::make_unique<PortAudioDaq>(*_info, config);
|
||||||
@ -224,136 +188,144 @@ std::unique_ptr<Daq> createPortAudioDevice(const DeviceInfo &devinfo,
|
|||||||
static int rawPaCallback(const void *inputBuffer, void *outputBuffer,
|
static int rawPaCallback(const void *inputBuffer, void *outputBuffer,
|
||||||
unsigned long framesPerBuffer,
|
unsigned long framesPerBuffer,
|
||||||
const PaStreamCallbackTimeInfo *timeInfo,
|
const PaStreamCallbackTimeInfo *timeInfo,
|
||||||
PaStreamCallbackFlags statusFlags, void *userData) {
|
PaStreamCallbackFlags statusFlags, void *userData)
|
||||||
|
{
|
||||||
return static_cast<PortAudioDaq *>(userData)->memberPaCallback(
|
return static_cast<PortAudioDaq *>(userData)->memberPaCallback(
|
||||||
inputBuffer, outputBuffer, framesPerBuffer, timeInfo, statusFlags);
|
inputBuffer, outputBuffer, framesPerBuffer, timeInfo, statusFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
PortAudioDaq::PortAudioDaq(const OurPaDeviceInfo &devinfo_gen,
|
PortAudioDaq::PortAudioDaq(const OurPaDeviceInfo &devinfo_gen,
|
||||||
const DaqConfiguration &config)
|
const DaqConfiguration &config)
|
||||||
: Daq(devinfo_gen, config) {
|
: Daq(devinfo_gen, config)
|
||||||
|
{
|
||||||
|
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
bool shouldPaTerminate = false;
|
PaError err = Pa_Initialize();
|
||||||
try {
|
/// PortAudio says that Pa_Terminate() should not be called whenever there
|
||||||
PaError err = Pa_Initialize();
|
/// is an error in Pa_Initialize(). This is opposite to what most examples
|
||||||
/// PortAudio says that Pa_Terminate() should not be called whenever there
|
/// of PortAudio show.
|
||||||
/// is an error in Pa_Initialize(). This is opposite to what most examples
|
throwIfError(err);
|
||||||
/// of PortAudio show.
|
|
||||||
throwIfError(err);
|
|
||||||
|
|
||||||
// OK, Pa_Initialize successfully finished, it means we have to clean up
|
// OK, Pa_Initialize successfully finished, it means we have to clean up with
|
||||||
// with Pa_Terminate in the destructor.
|
// Pa_Terminate in the destructor.
|
||||||
shouldPaTerminate = true;
|
_shouldPaTerminate = true;
|
||||||
|
|
||||||
// Going to find the device in the list. If its there, we have to retrieve
|
// Going to find the device in the list. If its there, we have to retrieve
|
||||||
// the index, as this is required in the PaStreamParameters struct
|
// the index, as this is required in the PaStreamParameters struct
|
||||||
int devindex = -1;
|
int devindex = -1;
|
||||||
for (int i = 0; i < Pa_GetDeviceCount(); i++) {
|
for (int i = 0; i < Pa_GetDeviceCount(); i++)
|
||||||
// DEBUGTRACE_PRINT(i);
|
{
|
||||||
bool ok = true;
|
// DEBUGTRACE_PRINT(i);
|
||||||
const PaDeviceInfo *info = Pa_GetDeviceInfo(i);
|
bool ok = true;
|
||||||
if (!info) {
|
const PaDeviceInfo *info = Pa_GetDeviceInfo(i);
|
||||||
throw rte("No device structure returned from PortAudio");
|
if (!info)
|
||||||
}
|
{
|
||||||
ok &= string(info->name) == devinfo_gen.device_name;
|
throw rte("No device structure returned from PortAudio");
|
||||||
ok &= info->hostApi == devinfo_gen._paDevInfo.hostApi;
|
|
||||||
ok &= info->maxInputChannels == devinfo_gen._paDevInfo.maxInputChannels;
|
|
||||||
ok &= info->maxOutputChannels == devinfo_gen._paDevInfo.maxOutputChannels;
|
|
||||||
ok &= info->defaultSampleRate == devinfo_gen._paDevInfo.defaultSampleRate;
|
|
||||||
|
|
||||||
if (ok) {
|
|
||||||
devindex = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (devindex < 0) {
|
|
||||||
throw rte(string("Device not found: ") + string(devinfo_gen.device_name));
|
|
||||||
}
|
}
|
||||||
|
ok &= string(info->name) == devinfo_gen.device_name;
|
||||||
|
ok &= info->hostApi == devinfo_gen._paDevInfo.hostApi;
|
||||||
|
ok &= info->maxInputChannels == devinfo_gen._paDevInfo.maxInputChannels;
|
||||||
|
ok &= info->maxOutputChannels == devinfo_gen._paDevInfo.maxOutputChannels;
|
||||||
|
ok &= info->defaultSampleRate == devinfo_gen._paDevInfo.defaultSampleRate;
|
||||||
|
|
||||||
using Dtype = DataTypeDescriptor::DataType;
|
if (ok)
|
||||||
const Dtype dtype = dataType();
|
{
|
||||||
// Sample format is bit flag
|
devindex = i;
|
||||||
PaSampleFormat format = paNonInterleaved;
|
|
||||||
switch (dtype) {
|
|
||||||
case Dtype::dtype_fl32:
|
|
||||||
DEBUGTRACE_PRINT("Datatype float32");
|
|
||||||
format |= paFloat32;
|
|
||||||
break;
|
|
||||||
case Dtype::dtype_fl64:
|
|
||||||
DEBUGTRACE_PRINT("Datatype float64");
|
|
||||||
throw rte("Invalid data type specified for DAQ stream.");
|
|
||||||
break;
|
|
||||||
case Dtype::dtype_int8:
|
|
||||||
DEBUGTRACE_PRINT("Datatype int8");
|
|
||||||
format |= paInt8;
|
|
||||||
break;
|
|
||||||
case Dtype::dtype_int16:
|
|
||||||
DEBUGTRACE_PRINT("Datatype int16");
|
|
||||||
format |= paInt16;
|
|
||||||
break;
|
|
||||||
case Dtype::dtype_int32:
|
|
||||||
DEBUGTRACE_PRINT("Datatype int32");
|
|
||||||
format |= paInt32;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw rte("Invalid data type specified for DAQ stream.");
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<PaStreamParameters> instreamParams;
|
|
||||||
std::unique_ptr<PaStreamParameters> outstreamParams;
|
|
||||||
|
|
||||||
if (neninchannels() > 0) {
|
|
||||||
instreamParams = std::make_unique<PaStreamParameters>(PaStreamParameters(
|
|
||||||
{.device = devindex,
|
|
||||||
.channelCount = (int)getHighestEnabledInChannel() + 1,
|
|
||||||
.sampleFormat = format,
|
|
||||||
.suggestedLatency = framesPerBlock() / samplerate(),
|
|
||||||
.hostApiSpecificStreamInfo = nullptr}));
|
|
||||||
}
|
|
||||||
if (nenoutchannels() > 0) {
|
|
||||||
outstreamParams = std::make_unique<PaStreamParameters>(PaStreamParameters(
|
|
||||||
{.device = devindex,
|
|
||||||
.channelCount = (int)getHighestEnabledOutChannel() + 1,
|
|
||||||
.sampleFormat = format,
|
|
||||||
.suggestedLatency = framesPerBlock() / samplerate(),
|
|
||||||
.hostApiSpecificStreamInfo = nullptr}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next step: check whether we are OK
|
|
||||||
err = Pa_IsFormatSupported(instreamParams.get(), outstreamParams.get(),
|
|
||||||
samplerate());
|
|
||||||
throwIfError(err);
|
|
||||||
|
|
||||||
err = Pa_OpenStream(&_stream, // stream
|
|
||||||
instreamParams.get(), // inputParameters
|
|
||||||
outstreamParams.get(), // outputParameters
|
|
||||||
samplerate(), // yeah,
|
|
||||||
framesPerBlock(), // framesPerBuffer
|
|
||||||
paNoFlag, // streamFlags
|
|
||||||
rawPaCallback, this);
|
|
||||||
throwIfError(err);
|
|
||||||
assert(_stream);
|
|
||||||
} catch (rte &e) {
|
|
||||||
if (shouldPaTerminate) {
|
|
||||||
PaError err = Pa_Terminate();
|
|
||||||
if (err != paNoError) {
|
|
||||||
cerr << "Error terminating PortAudio. Do not know what to do." << endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw;
|
|
||||||
}
|
}
|
||||||
|
if (devindex < 0)
|
||||||
|
{
|
||||||
|
throw rte(string("Device not found: ") + string(devinfo_gen.device_name));
|
||||||
|
}
|
||||||
|
|
||||||
|
using Dtype = DataTypeDescriptor::DataType;
|
||||||
|
const Dtype dtype = dataType();
|
||||||
|
// Sample format is bit flag
|
||||||
|
PaSampleFormat format = paNonInterleaved;
|
||||||
|
switch (dtype)
|
||||||
|
{
|
||||||
|
case Dtype::dtype_fl32:
|
||||||
|
DEBUGTRACE_PRINT("Datatype float32");
|
||||||
|
format |= paFloat32;
|
||||||
|
break;
|
||||||
|
case Dtype::dtype_fl64:
|
||||||
|
DEBUGTRACE_PRINT("Datatype float64");
|
||||||
|
throw rte("Invalid data type specified for DAQ stream.");
|
||||||
|
break;
|
||||||
|
case Dtype::dtype_int8:
|
||||||
|
DEBUGTRACE_PRINT("Datatype int8");
|
||||||
|
format |= paInt8;
|
||||||
|
break;
|
||||||
|
case Dtype::dtype_int16:
|
||||||
|
DEBUGTRACE_PRINT("Datatype int16");
|
||||||
|
format |= paInt16;
|
||||||
|
break;
|
||||||
|
case Dtype::dtype_int32:
|
||||||
|
DEBUGTRACE_PRINT("Datatype int32");
|
||||||
|
format |= paInt32;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw rte("Invalid data type specified for DAQ stream.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<PaStreamParameters> instreamParams;
|
||||||
|
std::unique_ptr<PaStreamParameters> outstreamParams;
|
||||||
|
|
||||||
|
if (neninchannels() > 0)
|
||||||
|
{
|
||||||
|
instreamParams = std::make_unique<PaStreamParameters>(
|
||||||
|
PaStreamParameters({.device = devindex,
|
||||||
|
.channelCount = (int)getHighestEnabledInChannel() + 1,
|
||||||
|
.sampleFormat = format,
|
||||||
|
.suggestedLatency = framesPerBlock() / samplerate(),
|
||||||
|
.hostApiSpecificStreamInfo = nullptr}));
|
||||||
|
}
|
||||||
|
if (nenoutchannels() > 0)
|
||||||
|
{
|
||||||
|
outstreamParams = std::make_unique<PaStreamParameters>(
|
||||||
|
PaStreamParameters({.device = devindex,
|
||||||
|
.channelCount = (int)getHighestEnabledOutChannel() + 1,
|
||||||
|
.sampleFormat = format,
|
||||||
|
.suggestedLatency = framesPerBlock() / samplerate(),
|
||||||
|
.hostApiSpecificStreamInfo = nullptr}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next step: check whether we are OK
|
||||||
|
err = Pa_IsFormatSupported(instreamParams.get(), outstreamParams.get(),
|
||||||
|
samplerate());
|
||||||
|
throwIfError(err);
|
||||||
|
|
||||||
|
err = Pa_OpenStream(&_stream, // stream
|
||||||
|
instreamParams.get(), // inputParameters
|
||||||
|
outstreamParams.get(), // outputParameters
|
||||||
|
samplerate(), // yeah,
|
||||||
|
framesPerBlock(), // framesPerBuffer
|
||||||
|
paNoFlag, // streamFlags
|
||||||
|
rawPaCallback, this);
|
||||||
|
throwIfError(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PortAudioDaq::start(InDaqCallback inCallback, OutDaqCallback outCallback) {
|
void PortAudioDaq::start(InDaqCallback inCallback, OutDaqCallback outCallback)
|
||||||
|
{
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
assert(_stream);
|
assert(_stream);
|
||||||
|
if (Pa_IsStreamActive(_stream))
|
||||||
if (Pa_IsStreamActive(_stream)) {
|
{
|
||||||
throw rte("Stream is already running");
|
throw rte("Stream is already running");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (neninchannels() > 0) {
|
// Logical XOR
|
||||||
if (!inCallback) {
|
if (inCallback && outCallback)
|
||||||
|
{
|
||||||
|
throw rte("Either input or output stream possible for RtAudio. "
|
||||||
|
"Stream duplex mode not provided.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (neninchannels() > 0)
|
||||||
|
{
|
||||||
|
if (!inCallback)
|
||||||
|
{
|
||||||
throw rte(
|
throw rte(
|
||||||
|
|
||||||
"Input callback given, but stream does not provide input data");
|
"Input callback given, but stream does not provide input data");
|
||||||
@ -361,8 +333,10 @@ void PortAudioDaq::start(InDaqCallback inCallback, OutDaqCallback outCallback) {
|
|||||||
|
|
||||||
_incallback = inCallback;
|
_incallback = inCallback;
|
||||||
}
|
}
|
||||||
if (nenoutchannels() > 0) {
|
if (nenoutchannels() > 0)
|
||||||
if (!outCallback) {
|
{
|
||||||
|
if (!outCallback)
|
||||||
|
{
|
||||||
throw rte(
|
throw rte(
|
||||||
"Output callback given, but stream does not provide output data");
|
"Output callback given, but stream does not provide output data");
|
||||||
}
|
}
|
||||||
@ -372,94 +346,86 @@ void PortAudioDaq::start(InDaqCallback inCallback, OutDaqCallback outCallback) {
|
|||||||
PaError err = Pa_StartStream(_stream);
|
PaError err = Pa_StartStream(_stream);
|
||||||
throwIfError(err);
|
throwIfError(err);
|
||||||
}
|
}
|
||||||
void PortAudioDaq::stop() {
|
void PortAudioDaq::stop()
|
||||||
|
{
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
assert(_stream);
|
assert(_stream);
|
||||||
if (Pa_IsStreamStopped(_stream) > 1) {
|
if (Pa_IsStreamStopped(_stream))
|
||||||
|
{
|
||||||
throw rte("Stream is already stopped");
|
throw rte("Stream is already stopped");
|
||||||
}
|
}
|
||||||
PaError err = Pa_StopStream(_stream);
|
PaError err = Pa_StopStream(_stream);
|
||||||
throwIfError(err);
|
throwIfError(err);
|
||||||
}
|
}
|
||||||
Daq::StreamStatus PortAudioDaq::getStreamStatus() const {
|
Daq::StreamStatus PortAudioDaq::getStreamStatus() const
|
||||||
DEBUGTRACE_ENTER;
|
{
|
||||||
// Stores an error type and whether the
|
|
||||||
Daq::StreamStatus status;
|
Daq::StreamStatus status;
|
||||||
using StreamError = Daq::StreamStatus::StreamError;
|
// Copy over atomic flag.
|
||||||
Daq::StreamStatus::StreamError errortype = _streamError.load();
|
status.errorType = _streamError;
|
||||||
|
// Check if stream is still running.
|
||||||
PaError err = Pa_IsStreamStopped(_stream);
|
if (_stream)
|
||||||
if (err > 1) {
|
{
|
||||||
// Stream is stopped due to an error in the callback. The exact error type
|
if (Pa_IsStreamActive(_stream))
|
||||||
// is filled in in the if-statement above
|
{
|
||||||
return status;
|
status.isRunning = true;
|
||||||
} else if (err == 0) {
|
|
||||||
// Still running
|
|
||||||
status.isRunning = true;
|
|
||||||
} else if (err < 0) {
|
|
||||||
// Stream encountered an error.
|
|
||||||
switch (err) {
|
|
||||||
case paInternalError:
|
|
||||||
errortype = StreamError::driverError;
|
|
||||||
break;
|
|
||||||
case paDeviceUnavailable:
|
|
||||||
errortype = StreamError::driverError;
|
|
||||||
break;
|
|
||||||
case paInputOverflowed:
|
|
||||||
errortype = StreamError::inputXRun;
|
|
||||||
break;
|
|
||||||
case paOutputUnderflowed:
|
|
||||||
errortype = StreamError::outputXRun;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
errortype = StreamError::driverError;
|
|
||||||
cerr << "Portaudio backend error:" << Pa_GetErrorText(err) << endl;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
status.errorType = errortype;
|
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
PortAudioDaq::~PortAudioDaq() {
|
PortAudioDaq::~PortAudioDaq()
|
||||||
|
{
|
||||||
PaError err;
|
PaError err;
|
||||||
assert(_stream);
|
if (_stream)
|
||||||
if (Pa_IsStreamActive(_stream)) {
|
{
|
||||||
// Stop the stream first
|
if (Pa_IsStreamActive(_stream))
|
||||||
stop();
|
{
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
err = Pa_CloseStream(_stream);
|
||||||
|
_stream = nullptr;
|
||||||
|
if (err != paNoError)
|
||||||
|
{
|
||||||
|
cerr << "Error closing PortAudio stream. Do not know what to do." << endl;
|
||||||
|
}
|
||||||
|
assert(_shouldPaTerminate);
|
||||||
}
|
}
|
||||||
|
|
||||||
err = Pa_CloseStream(_stream);
|
if (_shouldPaTerminate)
|
||||||
_stream = nullptr;
|
{
|
||||||
if (err != paNoError) {
|
err = Pa_Terminate();
|
||||||
cerr << "Error closing PortAudio stream. Do not know what to do." << endl;
|
if (err != paNoError)
|
||||||
}
|
{
|
||||||
|
cerr << "Error terminating PortAudio. Do not know what to do." << endl;
|
||||||
err = Pa_Terminate();
|
}
|
||||||
if (err != paNoError) {
|
|
||||||
cerr << "Error terminating PortAudio. Do not know what to do." << endl;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int PortAudioDaq::memberPaCallback(const void *inputBuffer, void *outputBuffer,
|
int PortAudioDaq::memberPaCallback(const void *inputBuffer, void *outputBuffer,
|
||||||
unsigned long framesPerBuffer,
|
unsigned long framesPerBuffer,
|
||||||
const PaStreamCallbackTimeInfo *timeInfo,
|
const PaStreamCallbackTimeInfo *timeInfo,
|
||||||
PaStreamCallbackFlags statusFlags) {
|
PaStreamCallbackFlags statusFlags)
|
||||||
// DEBUGTRACE_ENTER;
|
{
|
||||||
|
|
||||||
|
DEBUGTRACE_ENTER;
|
||||||
typedef Daq::StreamStatus::StreamError se;
|
typedef Daq::StreamStatus::StreamError se;
|
||||||
if (statusFlags & paPrimingOutput) {
|
if (statusFlags & paPrimingOutput)
|
||||||
|
{
|
||||||
// Initial output buffers generated. So nothing with input yet
|
// Initial output buffers generated. So nothing with input yet
|
||||||
return paContinue;
|
return paContinue;
|
||||||
}
|
}
|
||||||
if ((statusFlags & paInputUnderflow) || (statusFlags & paInputOverflow)) {
|
if ((statusFlags & paInputUnderflow) || (statusFlags & paInputOverflow))
|
||||||
|
{
|
||||||
_streamError = se::inputXRun;
|
_streamError = se::inputXRun;
|
||||||
return paAbort;
|
return paAbort;
|
||||||
}
|
}
|
||||||
if ((statusFlags & paOutputUnderflow) || (statusFlags & paOutputOverflow)) {
|
if ((statusFlags & paOutputUnderflow) || (statusFlags & paOutputOverflow))
|
||||||
|
{
|
||||||
_streamError = se::outputXRun;
|
_streamError = se::outputXRun;
|
||||||
return paAbort;
|
return paAbort;
|
||||||
}
|
}
|
||||||
if (framesPerBuffer != framesPerBlock()) {
|
if (framesPerBuffer != framesPerBlock())
|
||||||
|
{
|
||||||
cerr << "Logic error: expected a block size of: " << framesPerBlock()
|
cerr << "Logic error: expected a block size of: " << framesPerBlock()
|
||||||
<< endl;
|
<< endl;
|
||||||
_streamError = se::logicError;
|
_streamError = se::logicError;
|
||||||
@ -471,7 +437,8 @@ int PortAudioDaq::memberPaCallback(const void *inputBuffer, void *outputBuffer,
|
|||||||
const auto &dtype_descr = dtypeDescr();
|
const auto &dtype_descr = dtypeDescr();
|
||||||
const auto dtype = dataType();
|
const auto dtype = dataType();
|
||||||
const us sw = dtype_descr.sw;
|
const us sw = dtype_descr.sw;
|
||||||
if (inputBuffer) {
|
if (inputBuffer)
|
||||||
|
{
|
||||||
assert(_incallback);
|
assert(_incallback);
|
||||||
std::vector<byte_t *> ptrs;
|
std::vector<byte_t *> ptrs;
|
||||||
ptrs.reserve(neninchannels);
|
ptrs.reserve(neninchannels);
|
||||||
@ -483,8 +450,10 @@ int PortAudioDaq::memberPaCallback(const void *inputBuffer, void *outputBuffer,
|
|||||||
|
|
||||||
/// Only pass on the pointers of the channels we want. inputBuffer is
|
/// Only pass on the pointers of the channels we want. inputBuffer is
|
||||||
/// noninterleaved, as specified in PortAudioDaq constructor.
|
/// noninterleaved, as specified in PortAudioDaq constructor.
|
||||||
for (us ch = ch_min; ch <= ch_max; ch++) {
|
for (us ch = ch_min; ch <= ch_max; ch++)
|
||||||
if (inchannel_config.at(ch).enabled) {
|
{
|
||||||
|
if (inchannel_config.at(ch).enabled)
|
||||||
|
{
|
||||||
byte_t *ch_ptr =
|
byte_t *ch_ptr =
|
||||||
reinterpret_cast<byte_t **>(const_cast<void *>(inputBuffer))[ch];
|
reinterpret_cast<byte_t **>(const_cast<void *>(inputBuffer))[ch];
|
||||||
ptrs.push_back(ch_ptr);
|
ptrs.push_back(ch_ptr);
|
||||||
@ -496,7 +465,8 @@ int PortAudioDaq::memberPaCallback(const void *inputBuffer, void *outputBuffer,
|
|||||||
_incallback(d);
|
_incallback(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (outputBuffer) {
|
if (outputBuffer)
|
||||||
|
{
|
||||||
assert(_outcallback);
|
assert(_outcallback);
|
||||||
std::vector<byte_t *> ptrs;
|
std::vector<byte_t *> ptrs;
|
||||||
ptrs.reserve(nenoutchannels);
|
ptrs.reserve(nenoutchannels);
|
||||||
@ -508,8 +478,10 @@ int PortAudioDaq::memberPaCallback(const void *inputBuffer, void *outputBuffer,
|
|||||||
assert(ch_min < noutchannels);
|
assert(ch_min < noutchannels);
|
||||||
assert(ch_max < noutchannels);
|
assert(ch_max < noutchannels);
|
||||||
/// Only pass on the pointers of the channels we want
|
/// Only pass on the pointers of the channels we want
|
||||||
for (us ch = ch_min; ch <= ch_max; ch++) {
|
for (us ch = ch_min; ch <= ch_max; ch++)
|
||||||
if (outchannel_config.at(ch).enabled) {
|
{
|
||||||
|
if (outchannel_config.at(ch).enabled)
|
||||||
|
{
|
||||||
byte_t *ch_ptr = reinterpret_cast<byte_t **>(outputBuffer)[ch];
|
byte_t *ch_ptr = reinterpret_cast<byte_t **>(outputBuffer)[ch];
|
||||||
ptrs.push_back(ch_ptr);
|
ptrs.push_back(ch_ptr);
|
||||||
}
|
}
|
||||||
@ -519,7 +491,8 @@ int PortAudioDaq::memberPaCallback(const void *inputBuffer, void *outputBuffer,
|
|||||||
_outcallback(d);
|
_outcallback(d);
|
||||||
// Copy over the buffer
|
// Copy over the buffer
|
||||||
us j = 0;
|
us j = 0;
|
||||||
for (auto ptr : ptrs) {
|
for (auto ptr : ptrs)
|
||||||
|
{
|
||||||
d.copyToRaw(j, ptr);
|
d.copyToRaw(j, ptr);
|
||||||
j++;
|
j++;
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ requires-python = ">=3.10"
|
|||||||
description = "Library for Acoustic Signal Processing"
|
description = "Library for Acoustic Signal Processing"
|
||||||
license = { "file" = "LICENSE" }
|
license = { "file" = "LICENSE" }
|
||||||
authors = [{ "name" = "J.A. de Jong", "email" = "j.a.dejong@ascee.nl" }]
|
authors = [{ "name" = "J.A. de Jong", "email" = "j.a.dejong@ascee.nl" }]
|
||||||
version = "1.4.2"
|
version = "1.3.1"
|
||||||
|
|
||||||
keywords = ["DSP", "DAQ", "Signal processing"]
|
keywords = ["DSP", "DAQ", "Signal processing"]
|
||||||
|
|
||||||
@ -53,7 +53,7 @@ include = [
|
|||||||
build_type = "Release"
|
build_type = "Release"
|
||||||
source_path = "."
|
source_path = "."
|
||||||
options = { "LASP_HAS_PORTAUDIO" = "ON", "LASP_HAS_RTAUDIO" = "OFF" }
|
options = { "LASP_HAS_PORTAUDIO" = "ON", "LASP_HAS_RTAUDIO" = "OFF" }
|
||||||
build_args = ["-j"]
|
build_args = ["-j12"]
|
||||||
install_components = ["python_modules"]
|
install_components = ["python_modules"]
|
||||||
|
|
||||||
[tool.py-build-cmake.editable]
|
[tool.py-build-cmake.editable]
|
||||||
|
@ -57,9 +57,6 @@ class SLM:
|
|||||||
level_ref_value: Reference value for computing the levels in dB
|
level_ref_value: Reference value for computing the levels in dB
|
||||||
offset_t: Offset to be added to output time data [s]
|
offset_t: Offset to be added to output time data [s]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if tw == TimeWeighting.ufast:
|
|
||||||
raise RuntimeError('The current implementation of impulse time weigthing is incorrect, as it does not have the proper decay time constant.')
|
|
||||||
|
|
||||||
self.fbdesigner = fbdesigner
|
self.fbdesigner = fbdesigner
|
||||||
if xmin is None and fbdesigner is not None:
|
if xmin is None and fbdesigner is not None:
|
||||||
|
2
third_party/armadillo-code
vendored
2
third_party/armadillo-code
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 614f2a9c68998b11010dc1734a77f84fcbd6fa2d
|
Subproject commit b6e810f2d33bcc234d67db5277d027949fec82f8
|
2
third_party/rtaudio
vendored
2
third_party/rtaudio
vendored
@ -1 +1 @@
|
|||||||
Subproject commit b4f04903312e0e0efffbe77655172e0f060dc085
|
Subproject commit 46b01b5b134f33d8ddc3dab76829d4b1350e0522
|
Loading…
Reference in New Issue
Block a user