Compare commits

...

2 Commits

Author SHA1 Message Date
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
5 changed files with 305 additions and 304 deletions

View File

@ -4,7 +4,8 @@ if(LASP_HAS_PORTAUDIO)
if(WIN32) if(WIN32)
else() else()
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_USE_PULSEAUDIO FALSE CACHE BOOL "Build PortAudio with PulseAudio backend")
# set(PA_ALSA_DYNAMIC FALSE CACHE BOOL "Build static library of ALSA") # 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()

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

@ -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);
@ -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,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,34 +300,40 @@ 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(
"first stop existing stream"); "Error: an input stream is already running. Please "
"first stop existing stream");
} else if (isOutput && _outputStream) { } else if (isOutput && _outputStream) {
throw rte("Error: output stream is already running. Please " throw rte(
"first stop existing stream"); "Error: output stream is already running. Please "
"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(
"Please " "Error: output stream is already running (in duplex mode). "
"first stop existing stream"); "Please "
"first stop existing stream");
} }
} }
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(
"required, please enable duplex mode for this device"); "This device is already opened for output. If input is also "
"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(
"required, please enable duplex mode for this device"); "This device is already opened for input. If output is also "
"required, please enable duplex mode for this device");
} }
InDaqCallback inCallback; InDaqCallback inCallback;
@ -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);
} }
@ -371,7 +375,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) {
@ -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();
@ -423,7 +426,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
} }
} }
@ -436,8 +439,9 @@ 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(
"is called more than once on a handler object"); "Error: handler already added. Probably start() "
"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());
@ -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,16 +26,14 @@ 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 &operator=(const OurPaDeviceInfo &) = delete;
@ -44,44 +41,40 @@ public:
OurPaDeviceInfo(const PaDeviceInfo &o) : DeviceInfo(), _paDevInfo(o) {} 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(); const PaHostApiIndex apicount = Pa_GetHostApiCount();
if (apicount < 0) if (apicount < 0) {
{
return; 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(*deviceInfo);
@ -90,36 +83,36 @@ void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist)
d.device_name = deviceInfo->name; d.device_name = deviceInfo->name;
const PaHostApiInfo *hostapiinfo = Pa_GetHostApiInfo(deviceInfo->hostApi); const PaHostApiInfo *hostapiinfo = Pa_GetHostApiInfo(deviceInfo->hostApi);
if (hostapiinfo == nullptr) if (hostapiinfo == nullptr) {
{
throw std::runtime_error("Hostapi nullptr!"); throw std::runtime_error("Hostapi nullptr!");
} }
switch (hostapiinfo->type) switch (hostapiinfo->type) {
{ case paALSA:
case paALSA: // Duplex mode for alsa
d.api = portaudioALSAApi; hasDuplexMode = true;
break; d.api = portaudioALSAApi;
case paASIO: break;
d.api = portaudioASIOApi; case paASIO:
break; d.api = portaudioASIOApi;
case paDirectSound: break;
d.api = portaudioDirectSoundApi; case paDirectSound:
break; d.api = portaudioDirectSoundApi;
case paMME: break;
d.api = portaudioWMMEApi; case paMME:
break; d.api = portaudioWMMEApi;
case paWDMKS: break;
d.api = portaudioWDMKS; case paWDMKS:
break; d.api = portaudioWDMKS;
case paWASAPI: break;
d.api = portaudioWASAPIApi; case paWASAPI:
break; d.api = portaudioWASAPIApi;
case paPulseAudio: break;
d.api = portaudioPulseApi; case paPulseAudio:
break; d.api = portaudioPulseApi;
default: break;
throw rte("Unimplemented portaudio API!"); default:
break; throw rte("Unimplemented portaudio API!");
break;
} }
d.availableDataTypes = {DataTypeDescriptor::DataType::dtype_int16, d.availableDataTypes = {DataTypeDescriptor::DataType::dtype_int16,
@ -128,7 +121,7 @@ void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist)
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;
@ -148,8 +141,13 @@ void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist)
} }
} }
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;
} }
@ -174,16 +172,14 @@ 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);
@ -212,13 +208,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; 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);
@ -227,144 +221,143 @@ 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;
PaError err = Pa_Initialize(); bool shouldPaTerminate = false;
/// PortAudio says that Pa_Terminate() should not be called whenever there try {
/// is an error in Pa_Initialize(). This is opposite to what most examples PaError err = Pa_Initialize();
/// of PortAudio show. /// PortAudio says that Pa_Terminate() should not be called whenever there
throwIfError(err); /// is an error in Pa_Initialize(). This is opposite to what most examples
/// of PortAudio show.
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 &= 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;
}
} }
ok &= string(info->name) == devinfo_gen.device_name; if (devindex < 0) {
ok &= info->hostApi == devinfo_gen._paDevInfo.hostApi; throw rte(string("Device not found: ") + string(devinfo_gen.device_name));
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));
}
using Dtype = DataTypeDescriptor::DataType; using Dtype = DataTypeDescriptor::DataType;
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; break;
break; case Dtype::dtype_fl64:
case Dtype::dtype_fl64: DEBUGTRACE_PRINT("Datatype float64");
DEBUGTRACE_PRINT("Datatype float64"); throw rte("Invalid data type specified for DAQ stream.");
throw rte("Invalid data type specified for DAQ stream."); break;
break; case Dtype::dtype_int8:
case Dtype::dtype_int8: DEBUGTRACE_PRINT("Datatype int8");
DEBUGTRACE_PRINT("Datatype int8"); format |= paInt8;
format |= paInt8; break;
break; case Dtype::dtype_int16:
case Dtype::dtype_int16: DEBUGTRACE_PRINT("Datatype int16");
DEBUGTRACE_PRINT("Datatype int16"); format |= paInt16;
format |= paInt16; break;
break; case Dtype::dtype_int32:
case Dtype::dtype_int32: DEBUGTRACE_PRINT("Datatype int32");
DEBUGTRACE_PRINT("Datatype int32"); format |= paInt32;
format |= paInt32; break;
break; default:
default: throw rte("Invalid data type specified for DAQ stream.");
throw rte("Invalid data type specified for DAQ stream."); break;
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;
} }
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");
} }
// Logical XOR // Logical XOR
if (inCallback && outCallback) if (inCallback && outCallback) {
{ throw rte(
throw rte("Either input or output stream possible for RtAudio. " "Either input or output stream possible for PortAudio. "
"Stream duplex mode not provided."); "Stream duplex mode not provided.");
} }
if (neninchannels() > 0) if (neninchannels() > 0) {
{ if (!inCallback) {
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");
@ -372,10 +365,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");
} }
@ -385,86 +376,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
status.isRunning = true; return status;
} 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;
if (_stream) assert(_stream);
{ if (Pa_IsStreamActive(_stream)) {
if (Pa_IsStreamActive(_stream)) // Stop the stream first
{ 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);
} }
if (_shouldPaTerminate) err = Pa_CloseStream(_stream);
{ _stream = nullptr;
err = Pa_Terminate(); if (err != paNoError) {
if (err != paNoError) cerr << "Error closing PortAudio stream. Do not know what to do." << endl;
{ }
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;
@ -476,8 +475,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);
@ -489,10 +487,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);
@ -504,8 +500,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);
@ -517,10 +512,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);
} }
@ -530,8 +523,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

@ -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]