Compare commits

...

7 Commits

Author SHA1 Message Date
e9f500d460 Small change in portaudio.cmake
Some checks failed
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Failing after 2m42s
2024-02-20 15:47:12 +01:00
6bda124196 Allow duplex mode for PortAudio ALSA devices
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m53s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-02-06 15:02:25 +01:00
7ce45e9c82 Some comment improvements, and portaudio API improvements. Also, disabled PortAudio PulseAudio backend as it is not working properly.
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m35s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Successful in -31s
2024-02-06 14:59:51 +01:00
7c8e6368ba Removed accidental use of wrong time weighting for impulse (35 ms). 2024-02-06 11:22:31 +01:00
7430e2c600 Updated armadillo
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 3m0s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-01-30 14:40:43 +01:00
6b337df2a9 Bugfix in channel counter for getHighestEnabledOutChannel, added Api -subapis for Portaudio backend. Switch to defaulting Portaudio as audio backend. Added PulseAudio as extra sub-api to default compile in portaudio
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 3m2s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-01-25 15:31:53 +01:00
c713806bbe RtAudio not updated to 6.0.1?
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m52s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-01-23 15:18:04 +01:00
13 changed files with 372 additions and 307 deletions

View File

@ -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 ON) set(DEFAULT_RTAUDIO OFF)
set(DEFAULT_PORTAUDIO OFF) set(DEFAULT_PORTAUDIO ON)
set(DEFAULT_ULDAQ ON) set(DEFAULT_ULDAQ ON)
endif() endif()

View File

@ -3,9 +3,10 @@ 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 TRUE CACHE BOOL "Build PortAudio with Jack backend") set(PA_USE_JACK FALSE CACHE BOOL "Build PortAudio with Jack backend")
# set(PA_ALSA_DYNAMIC FALSE CACHE BOOL "Build static library of ALSA") set(PA_USE_PULSEAUDIO FALSE CACHE BOOL "Build PortAudio with PulseAudio backend")
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)

View File

@ -38,6 +38,7 @@ 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 EXCLUDE_FROM_ALL
@ -51,3 +52,4 @@ if (WIN32)
DESTINATION ${PY_BUILD_CMAKE_MODULE_NAME} DESTINATION ${PY_BUILD_CMAKE_MODULE_NAME}
OPTIONAL) OPTIONAL)
endif() endif()
endif()

View File

@ -56,13 +56,29 @@ 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) {
{
const int hich = getHighestEnabledInChannel();
if (hich + 1 > devinfo.ninchannels)
{
throw rte( throw rte(
"Number of enabled input channels is higher than device capability"); string("Highest of enabled input channel: ") +
to_string(hich) +
string(" is higher than device capability, which is: ") +
to_string(ninchannels) + ".");
} }
if (nenoutchannels() > devinfo.noutchannels) { }
{
const int hoch = getHighestEnabledOutChannel();
if (hoch + 1 > devinfo.noutchannels)
{
throw rte( throw rte(
"Number of enabled output channels is higher than device capability"); string("Highest of enabled output channel: ") +
to_string(hoch) +
string(" is higher than device capability, which is: ") +
to_string(noutchannels) + ".");
}
} }
} }

View File

@ -48,6 +48,10 @@ 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
*/ */
@ -61,7 +65,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
* *
@ -69,8 +73,6 @@ 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); }
/** /**

View File

@ -56,10 +56,12 @@ 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;
@ -68,13 +70,15 @@ int DaqConfiguration::getHighestEnabledInChannel() const {
} }
int DaqConfiguration::getHighestEnabledOutChannel() const { int DaqConfiguration::getHighestEnabledOutChannel() const {
for (us i = outchannel_config.size() - 1; i >= 0; i--) { DEBUGTRACE_ENTER;
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;

View File

@ -148,7 +148,14 @@ 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 portaudioApi("PortAudio Linux ALSA", LASP_PORTAUDIO_APICODE, 0); const DaqApi portaudioALSAApi("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;

View File

@ -1,16 +1,19 @@
/* #define DEBUGTRACE_ENABLED */ /* #define DEBUGTRACE_ENABLED */
#include "lasp_streammgr.h" #include "lasp_streammgr.h"
#include "debugtrace.hpp"
#include "lasp_biquadbank.h"
#include "lasp_indatahandler.h"
#include "lasp_thread.h"
#include <algorithm>
#include <assert.h> #include <assert.h>
#include <algorithm>
#include <functional> #include <functional>
#include <iostream> #include <iostream>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include "debugtrace.hpp"
#include "lasp_biquadbank.h"
#include "lasp_indatahandler.h"
#include "lasp_thread.h"
using std::cerr; using std::cerr;
using std::endl; using std::endl;
using rte = std::runtime_error; using rte = std::runtime_error;
@ -109,7 +112,6 @@ 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);
@ -118,7 +120,6 @@ 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.
@ -151,7 +152,6 @@ 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,7 +179,8 @@ void StreamMgr::setSiggen(std::shared_ptr<Siggen> siggen) {
* *
* @return * @return
*/ */
template <typename T> bool fillData(DaqData &data, const vd &signal) { template <typename T>
bool fillData(DaqData &data, const vd &signal) {
/* DEBUGTRACE_ENTER; */ /* DEBUGTRACE_ENTER; */
assert(data.nframes == signal.size()); assert(data.nframes == signal.size());
@ -212,7 +213,6 @@ template <typename T> 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);
@ -257,8 +257,6 @@ 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;
@ -273,11 +271,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; }); [](auto &i) { return i.enabled; }) > 0;
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; }); [](auto &i) { return i.enabled; }) > 0;
// 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);
@ -302,19 +300,23 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
bool isDuplex = isInput && isOutput; bool isDuplex = isInput && isOutput;
if (!isInput && !isOutput) { if (!isInput && !isOutput) {
throw rte("Neither input, nor output channels enabled for " throw rte(
"stream. Cannot start."); "Attempted stream start failed, stream does not have any enabled "
"channels. Please first enable channels in the channel configuration.");
} }
if (isInput && _inputStream) { if (isInput && _inputStream) {
throw rte("Error: an input stream is already running. Please " throw rte(
"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("Error: output stream is already running. Please " throw rte(
"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("Error: output stream is already running (in duplex mode). " throw rte(
"Error: output stream is already running (in duplex mode). "
"Please " "Please "
"first stop existing stream"); "first stop existing stream");
} }
@ -322,13 +324,15 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
if (_outputStream && isInput && _outputStream->duplexModeForced && if (_outputStream && isInput && _outputStream->duplexModeForced &&
config.match(*_outputStream)) { config.match(*_outputStream)) {
throw rte("This device is already opened for output. If input is also " throw rte(
"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("This device is already opened for input. If output is also " throw rte(
"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");
} }
@ -353,8 +357,8 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
/// Create input filters /// Create input filters
_inputFilters.clear(); _inputFilters.clear();
/// No input filter for monitor channel, which comes as the first input channel /// No input filter for monitor channel, which comes as the first input
/// In the list /// channel In the list
if (config.monitorOutput && devinfo->hasInternalOutputMonitor) { if (config.monitorOutput && devinfo->hasInternalOutputMonitor) {
_inputFilters.push_back(nullptr); _inputFilters.push_back(nullptr);
} }
@ -398,7 +402,6 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
} }
} }
void StreamMgr::stopStream(const StreamType t) { void StreamMgr::stopStream(const StreamType t) {
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
checkRightThread(); checkRightThread();
@ -436,7 +439,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("Error: handler already added. Probably start() " throw std::runtime_error(
"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);
@ -467,7 +471,6 @@ 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) {

View File

@ -3,23 +3,22 @@
#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));
} }
} }
@ -27,62 +26,95 @@ 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;
try bool shouldPaTerminate = false;
{ 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; OurPaDeviceInfo d(*deviceInfo);
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};
@ -105,12 +137,20 @@ 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;
} }
@ -135,9 +175,7 @@ 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;
@ -173,13 +211,11 @@ public:
}; };
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);
@ -188,38 +224,35 @@ 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;
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);
// OK, Pa_Initialize successfully finished, it means we have to clean up with // OK, Pa_Initialize successfully finished, it means we have to clean up
// Pa_Terminate in the destructor. // with 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); // DEBUGTRACE_PRINT(i);
bool ok = true; bool ok = true;
const PaDeviceInfo *info = Pa_GetDeviceInfo(i); const PaDeviceInfo *info = Pa_GetDeviceInfo(i);
if (!info) if (!info) {
{
throw rte("No device structure returned from PortAudio"); throw rte("No device structure returned from PortAudio");
} }
ok &= string(info->name) == devinfo_gen.device_name; ok &= string(info->name) == devinfo_gen.device_name;
@ -228,13 +261,11 @@ PortAudioDaq::PortAudioDaq(const OurPaDeviceInfo &devinfo_gen,
ok &= info->maxOutputChannels == devinfo_gen._paDevInfo.maxOutputChannels; ok &= info->maxOutputChannels == devinfo_gen._paDevInfo.maxOutputChannels;
ok &= info->defaultSampleRate == devinfo_gen._paDevInfo.defaultSampleRate; ok &= info->defaultSampleRate == devinfo_gen._paDevInfo.defaultSampleRate;
if (ok) if (ok) {
{
devindex = i; devindex = i;
} }
} }
if (devindex < 0) if (devindex < 0) {
{
throw rte(string("Device not found: ") + string(devinfo_gen.device_name)); throw rte(string("Device not found: ") + string(devinfo_gen.device_name));
} }
@ -242,8 +273,7 @@ PortAudioDaq::PortAudioDaq(const OurPaDeviceInfo &devinfo_gen,
const Dtype dtype = dataType(); const Dtype dtype = dataType();
// Sample format is bit flag // Sample format is bit flag
PaSampleFormat format = paNonInterleaved; PaSampleFormat format = paNonInterleaved;
switch (dtype) switch (dtype) {
{
case Dtype::dtype_fl32: case Dtype::dtype_fl32:
DEBUGTRACE_PRINT("Datatype float32"); DEBUGTRACE_PRINT("Datatype float32");
format |= paFloat32; format |= paFloat32;
@ -272,19 +302,17 @@ PortAudioDaq::PortAudioDaq(const OurPaDeviceInfo &devinfo_gen,
std::unique_ptr<PaStreamParameters> instreamParams; std::unique_ptr<PaStreamParameters> instreamParams;
std::unique_ptr<PaStreamParameters> outstreamParams; std::unique_ptr<PaStreamParameters> outstreamParams;
if (neninchannels() > 0) if (neninchannels() > 0) {
{ instreamParams = std::make_unique<PaStreamParameters>(PaStreamParameters(
instreamParams = std::make_unique<PaStreamParameters>( {.device = devindex,
PaStreamParameters({.device = devindex,
.channelCount = (int)getHighestEnabledInChannel() + 1, .channelCount = (int)getHighestEnabledInChannel() + 1,
.sampleFormat = format, .sampleFormat = format,
.suggestedLatency = framesPerBlock() / samplerate(), .suggestedLatency = framesPerBlock() / samplerate(),
.hostApiSpecificStreamInfo = nullptr})); .hostApiSpecificStreamInfo = nullptr}));
} }
if (nenoutchannels() > 0) if (nenoutchannels() > 0) {
{ outstreamParams = std::make_unique<PaStreamParameters>(PaStreamParameters(
outstreamParams = std::make_unique<PaStreamParameters>( {.device = devindex,
PaStreamParameters({.device = devindex,
.channelCount = (int)getHighestEnabledOutChannel() + 1, .channelCount = (int)getHighestEnabledOutChannel() + 1,
.sampleFormat = format, .sampleFormat = format,
.suggestedLatency = framesPerBlock() / samplerate(), .suggestedLatency = framesPerBlock() / samplerate(),
@ -304,28 +332,28 @@ PortAudioDaq::PortAudioDaq(const OurPaDeviceInfo &devinfo_gen,
paNoFlag, // streamFlags paNoFlag, // streamFlags
rawPaCallback, this); rawPaCallback, this);
throwIfError(err); 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;
}
} }
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");
} }
// Logical XOR if (neninchannels() > 0) {
if (inCallback && outCallback) if (!inCallback) {
{
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");
@ -333,10 +361,8 @@ 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");
} }
@ -346,86 +372,94 @@ 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)) if (Pa_IsStreamStopped(_stream) > 1) {
{
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;
// Copy over atomic flag. using StreamError = Daq::StreamStatus::StreamError;
status.errorType = _streamError; Daq::StreamStatus::StreamError errortype = _streamError.load();
// Check if stream is still running.
if (_stream) PaError err = Pa_IsStreamStopped(_stream);
{ if (err > 1) {
if (Pa_IsStreamActive(_stream)) // Stream is stopped due to an error in the callback. The exact error type
{ // is filled in in the if-statement above
return status;
} else if (err == 0) {
// Still running
status.isRunning = true; 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;
if (_stream) assert(_stream);
{ if (Pa_IsStreamActive(_stream)) {
if (Pa_IsStreamActive(_stream)) // Stop the stream first
{
stop(); stop();
} }
err = Pa_CloseStream(_stream); err = Pa_CloseStream(_stream);
_stream = nullptr; _stream = nullptr;
if (err != paNoError) if (err != paNoError) {
{
cerr << "Error closing PortAudio stream. Do not know what to do." << endl; cerr << "Error closing PortAudio stream. Do not know what to do." << endl;
} }
assert(_shouldPaTerminate);
}
if (_shouldPaTerminate)
{
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;
} }
} }
}
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;
@ -437,8 +471,7 @@ 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);
@ -450,10 +483,8 @@ 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);
@ -465,8 +496,7 @@ 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);
@ -478,10 +508,8 @@ 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);
} }
@ -491,8 +519,7 @@ 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++;
} }

View File

@ -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.3.1" version = "1.4.2"
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 = ["-j12"] build_args = ["-j"]
install_components = ["python_modules"] install_components = ["python_modules"]
[tool.py-build-cmake.editable] [tool.py-build-cmake.editable]

View File

@ -58,6 +58,9 @@ class SLM:
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:
xmin = fbdesigner.xs[0] xmin = fbdesigner.xs[0]

@ -1 +1 @@
Subproject commit b6e810f2d33bcc234d67db5277d027949fec82f8 Subproject commit 614f2a9c68998b11010dc1734a77f84fcbd6fa2d

2
third_party/rtaudio vendored

@ -1 +1 @@
Subproject commit 46b01b5b134f33d8ddc3dab76829d4b1350e0522 Subproject commit b4f04903312e0e0efffbe77655172e0f060dc085