Compare commits

...

2 Commits

Author SHA1 Message Date
Anne de Jong 7ce45e9c82 Some comment improvements, and portaudio API improvements. Also, disabled PortAudio PulseAudio backend as it is not working properly.
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m35s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Successful in -31s Details
2024-02-06 14:59:51 +01:00
Anne de Jong 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)
else()
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_BUILD_SHARED_LIBS FALSE CACHE BOOL "Build static library")
endif()

View File

@ -48,6 +48,10 @@ public:
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
*/
@ -61,7 +65,7 @@ public:
{StreamError::logicError, "Logic error (probably a bug)"},
};
bool isRunning = false;
/**
* @brief Check if stream has error
*
@ -69,8 +73,6 @@ public:
*/
bool error() const { return errorType != StreamError::noError; };
StreamError errorType{StreamError::noError};
std::string errorMsg() const { return errorMessages.at(errorType); }
/**

View File

@ -1,16 +1,19 @@
/* #define DEBUGTRACE_ENABLED */
#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 <algorithm>
#include <functional>
#include <iostream>
#include <memory>
#include <mutex>
#include "debugtrace.hpp"
#include "lasp_biquadbank.h"
#include "lasp_indatahandler.h"
#include "lasp_thread.h"
using std::cerr;
using std::endl;
using rte = std::runtime_error;
@ -109,7 +112,6 @@ void StreamMgr::rescanDAQDevices_impl(std::function<void()> callback) {
}
}
void StreamMgr::inCallback(const DaqData &data) {
DEBUGTRACE_ENTER;
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(),
[](const auto &a) { return bool(a); }) > 0) {
/// Found a filter in vector of input filters. So we have to apply the
/// filters to each channel.
@ -151,7 +152,6 @@ void StreamMgr::inCallback(const DaqData &data) {
}
void StreamMgr::setSiggen(std::shared_ptr<Siggen> siggen) {
DEBUGTRACE_ENTER;
checkRightThread();
@ -179,7 +179,8 @@ void StreamMgr::setSiggen(std::shared_ptr<Siggen> siggen) {
*
* @return
*/
template <typename T> bool fillData(DaqData &data, const vd &signal) {
template <typename T>
bool fillData(DaqData &data, const vd &signal) {
/* DEBUGTRACE_ENTER; */
assert(data.nframes == signal.size());
@ -212,7 +213,6 @@ template <typename T> bool fillData(DaqData &data, const vd &signal) {
return true;
}
void StreamMgr::outCallback(DaqData &data) {
/* DEBUGTRACE_ENTER; */
std::scoped_lock<std::mutex> lck(_siggen_mtx);
@ -220,21 +220,21 @@ void StreamMgr::outCallback(DaqData &data) {
if (_siggen) {
vd signal = _siggen->genSignal(data.nframes);
switch (data.dtype) {
case (DataTypeDescriptor::DataType::dtype_fl32):
fillData<float>(data, signal);
break;
case (DataTypeDescriptor::DataType::dtype_fl64):
fillData<double>(data, signal);
break;
case (DataTypeDescriptor::DataType::dtype_int8):
fillData<int8_t>(data, signal);
break;
case (DataTypeDescriptor::DataType::dtype_int16):
fillData<int16_t>(data, signal);
break;
case (DataTypeDescriptor::DataType::dtype_int32):
fillData<int32_t>(data, signal);
break;
case (DataTypeDescriptor::DataType::dtype_fl32):
fillData<float>(data, signal);
break;
case (DataTypeDescriptor::DataType::dtype_fl64):
fillData<double>(data, signal);
break;
case (DataTypeDescriptor::DataType::dtype_int8):
fillData<int8_t>(data, signal);
break;
case (DataTypeDescriptor::DataType::dtype_int16):
fillData<int16_t>(data, signal);
break;
case (DataTypeDescriptor::DataType::dtype_int32):
fillData<int32_t>(data, signal);
break;
}
} else {
// Set all values to 0.
@ -257,8 +257,6 @@ StreamMgr::~StreamMgr() {
// virtual methods. This was really a bug.
_inputStream.reset();
_outputStream.reset();
}
void StreamMgr::stopAllStreams() {
DEBUGTRACE_ENTER;
@ -273,11 +271,11 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
bool isInput = std::count_if(config.inchannel_config.cbegin(),
config.inchannel_config.cend(),
[](auto &i) { return i.enabled; });
[](auto &i) { return i.enabled; }) > 0;
bool isOutput = std::count_if(config.outchannel_config.cbegin(),
config.outchannel_config.cend(),
[](auto &i) { return i.enabled; });
[](auto &i) { return i.enabled; }) > 0;
// Find the first device that matches with the configuration
std::scoped_lock lck(_devices_mtx);
@ -302,34 +300,40 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
bool isDuplex = isInput && isOutput;
if (!isInput && !isOutput) {
throw rte("Neither input, nor output channels enabled for "
"stream. Cannot start.");
throw rte(
"Attempted stream start failed, stream does not have any enabled "
"channels. Please first enable channels in the channel configuration.");
}
if (isInput && _inputStream) {
throw rte("Error: an input stream is already running. Please "
"first stop existing stream");
throw rte(
"Error: an input stream is already running. Please "
"first stop existing stream");
} else if (isOutput && _outputStream) {
throw rte("Error: output stream is already running. Please "
"first stop existing stream");
throw rte(
"Error: output stream is already running. Please "
"first stop existing stream");
} else if (_inputStream) {
if (_inputStream->duplexMode() && isOutput) {
throw rte("Error: output stream is already running (in duplex mode). "
"Please "
"first stop existing stream");
throw rte(
"Error: output stream is already running (in duplex mode). "
"Please "
"first stop existing stream");
}
}
if (_outputStream && isInput && _outputStream->duplexModeForced &&
config.match(*_outputStream)) {
throw rte("This device is already opened for output. If input is also "
"required, please enable duplex mode for this device");
throw rte(
"This device is already opened for output. If input is also "
"required, please enable duplex mode for this device");
}
if (_inputStream && isOutput && _inputStream->duplexModeForced &&
config.match(*_inputStream)) {
throw rte("This device is already opened for input. If output is also "
"required, please enable duplex mode for this device");
throw rte(
"This device is already opened for input. If output is also "
"required, please enable duplex mode for this device");
}
InDaqCallback inCallback;
@ -352,13 +356,13 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
d fs = daq->samplerate();
/// Create input filters
_inputFilters.clear();
/// No input filter for monitor channel, which comes as the first input channel
/// In the list
/// No input filter for monitor channel, which comes as the first input
/// channel In the list
if (config.monitorOutput && devinfo->hasInternalOutputMonitor) {
_inputFilters.push_back(nullptr);
}
for (auto &ch : daq->inchannel_config) {
if (ch.enabled) {
if (ch.digitalHighPassCutOn < 0) {
@ -371,7 +375,7 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
SeriesBiquad::firstOrderHighPass(fs, ch.digitalHighPassCutOn)));
}
}
} // End of input filter creation
} // End of input filter creation
}
if (isOutput) {
@ -398,7 +402,6 @@ void StreamMgr::startStream(const DaqConfiguration &config) {
}
}
void StreamMgr::stopStream(const StreamType t) {
DEBUGTRACE_ENTER;
checkRightThread();
@ -423,7 +426,7 @@ void StreamMgr::stopStream(const StreamType t) {
throw rte("Output stream is not running");
}
_outputStream.reset();
} // end else
} // end else
}
}
@ -436,8 +439,9 @@ void StreamMgr::addInDataHandler(InDataHandler *handler) {
if (std::find(_inDataHandlers.cbegin(), _inDataHandlers.cend(), handler) !=
_inDataHandlers.cend()) {
throw std::runtime_error("Error: handler already added. Probably start() "
"is called more than once on a handler object");
throw std::runtime_error(
"Error: handler already added. Probably start() "
"is called more than once on a handler object");
}
_inDataHandlers.push_back(handler);
DEBUGTRACE_PRINT(_inDataHandlers.size());
@ -467,7 +471,6 @@ Daq::StreamStatus StreamMgr::getStreamStatus(const StreamType type) const {
}
const Daq *StreamMgr::getDaq(StreamType type) const {
checkRightThread();
if (type == StreamType::input) {

View File

@ -3,23 +3,22 @@
#include "lasp_config.h"
#if LASP_HAS_PORTAUDIO == 1
#include "lasp_portaudiodaq.h"
#include "portaudio.h"
#include <gsl-lite/gsl-lite.hpp>
#include <mutex>
#include <string>
#include "lasp_portaudiodaq.h"
#include "portaudio.h"
using rte = std::runtime_error;
using std::cerr;
using std::endl;
using std::string;
using std::to_string;
inline void throwIfError(PaError e)
{
inline void throwIfError(PaError e) {
DEBUGTRACE_ENTER;
if (e != paNoError)
{
if (e != paNoError) {
throw rte(string("PortAudio backend error: ") + Pa_GetErrorText(e));
}
}
@ -27,16 +26,14 @@ inline void throwIfError(PaError e)
/**
* @brief Device info, plus PortAudio stuff
*/
class OurPaDeviceInfo : public DeviceInfo
{
public:
class OurPaDeviceInfo : public DeviceInfo {
public:
/**
* @brief Store instance to PaDeviceInfo.
*/
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);
}
OurPaDeviceInfo &operator=(const OurPaDeviceInfo &) = delete;
@ -44,44 +41,40 @@ public:
OurPaDeviceInfo(const PaDeviceInfo &o) : DeviceInfo(), _paDevInfo(o) {}
};
void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist)
{
void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) {
DEBUGTRACE_ENTER;
try
{
bool shouldPaTerminate = false;
try {
PaError err = Pa_Initialize();
/// PortAudio says that Pa_Terminate() should not be called whenever there
/// is an error in Pa_Initialize(). This is opposite to what most examples
/// of PortAudio show.
throwIfError(err);
shouldPaTerminate = true;
auto fin = gsl::finally([&err]
{
auto fin = gsl::finally([&err] {
DEBUGTRACE_PRINT("Terminating PortAudio instance");
err = Pa_Terminate();
if (err != paNoError) {
cerr << "Error terminating PortAudio. Do not know what to do." << endl;
} });
}
});
const PaHostApiIndex apicount = Pa_GetHostApiCount();
if (apicount < 0)
{
if (apicount < 0) {
return;
}
/* const PaDeviceInfo *deviceInfo; */
const int numDevices = Pa_GetDeviceCount();
if (numDevices < 0)
{
if (numDevices < 0) {
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); */
bool hasDuplexMode = false;
const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(i);
if (!deviceInfo)
{
if (!deviceInfo) {
throw rte("No device info struct returned");
}
OurPaDeviceInfo d(*deviceInfo);
@ -90,36 +83,36 @@ void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist)
d.device_name = deviceInfo->name;
const PaHostApiInfo *hostapiinfo = Pa_GetHostApiInfo(deviceInfo->hostApi);
if (hostapiinfo == nullptr)
{
if (hostapiinfo == nullptr) {
throw std::runtime_error("Hostapi nullptr!");
}
switch (hostapiinfo->type)
{
case paALSA:
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;
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,
@ -128,7 +121,7 @@ void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist)
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,
88200.0, 96000.0, 192000.0};
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;
return;
}
@ -174,16 +172,14 @@ static int rawPaCallback(const void *inputBuffer, void *outputBuffer,
const PaStreamCallbackTimeInfo *timeInfo,
PaStreamCallbackFlags statusFlags, void *userData);
class PortAudioDaq : public Daq
{
bool _shouldPaTerminate = false;
class PortAudioDaq : public Daq {
PaStream *_stream = nullptr;
std::atomic<StreamStatus::StreamError> _streamError =
StreamStatus::StreamError::noError;
InDaqCallback _incallback;
OutDaqCallback _outcallback;
public:
public:
PortAudioDaq(const OurPaDeviceInfo &devinfo_gen,
const DaqConfiguration &config);
@ -212,13 +208,11 @@ public:
};
std::unique_ptr<Daq> createPortAudioDevice(const DeviceInfo &devinfo,
const DaqConfiguration &config)
{
const DaqConfiguration &config) {
DEBUGTRACE_ENTER;
const OurPaDeviceInfo *_info =
dynamic_cast<const OurPaDeviceInfo *>(&devinfo);
if (_info == nullptr)
{
if (_info == nullptr) {
throw rte("BUG: Could not cast DeviceInfo to OurPaDeviceInfo");
}
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,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo *timeInfo,
PaStreamCallbackFlags statusFlags, void *userData)
{
PaStreamCallbackFlags statusFlags, void *userData) {
return static_cast<PortAudioDaq *>(userData)->memberPaCallback(
inputBuffer, outputBuffer, framesPerBuffer, timeInfo, statusFlags);
}
PortAudioDaq::PortAudioDaq(const OurPaDeviceInfo &devinfo_gen,
const DaqConfiguration &config)
: Daq(devinfo_gen, config)
{
: Daq(devinfo_gen, config) {
DEBUGTRACE_ENTER;
PaError err = Pa_Initialize();
/// PortAudio says that Pa_Terminate() should not be called whenever there
/// is an error in Pa_Initialize(). This is opposite to what most examples
/// of PortAudio show.
throwIfError(err);
bool shouldPaTerminate = false;
try {
PaError err = Pa_Initialize();
/// PortAudio says that Pa_Terminate() should not be called whenever there
/// 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
// Pa_Terminate in the destructor.
_shouldPaTerminate = true;
// OK, Pa_Initialize successfully finished, it means we have to clean up
// with Pa_Terminate in the destructor.
shouldPaTerminate = true;
// 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
int devindex = -1;
for (int i = 0; i < Pa_GetDeviceCount(); i++)
{
// DEBUGTRACE_PRINT(i);
bool ok = true;
const PaDeviceInfo *info = Pa_GetDeviceInfo(i);
if (!info)
{
throw rte("No device structure returned from PortAudio");
// 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
int devindex = -1;
for (int i = 0; i < Pa_GetDeviceCount(); i++) {
// DEBUGTRACE_PRINT(i);
bool ok = true;
const PaDeviceInfo *info = Pa_GetDeviceInfo(i);
if (!info) {
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;
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));
}
}
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;
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);
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;
assert(_stream);
if (Pa_IsStreamActive(_stream))
{
if (Pa_IsStreamActive(_stream)) {
throw rte("Stream is already running");
}
// Logical XOR
if (inCallback && outCallback)
{
throw rte("Either input or output stream possible for RtAudio. "
"Stream duplex mode not provided.");
if (inCallback && outCallback) {
throw rte(
"Either input or output stream possible for PortAudio. "
"Stream duplex mode not provided.");
}
if (neninchannels() > 0)
{
if (!inCallback)
{
if (neninchannels() > 0) {
if (!inCallback) {
throw rte(
"Input callback given, but stream does not provide input data");
@ -372,10 +365,8 @@ void PortAudioDaq::start(InDaqCallback inCallback, OutDaqCallback outCallback)
_incallback = inCallback;
}
if (nenoutchannels() > 0)
{
if (!outCallback)
{
if (nenoutchannels() > 0) {
if (!outCallback) {
throw rte(
"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);
throwIfError(err);
}
void PortAudioDaq::stop()
{
void PortAudioDaq::stop() {
DEBUGTRACE_ENTER;
assert(_stream);
if (Pa_IsStreamStopped(_stream))
{
if (Pa_IsStreamStopped(_stream) > 1) {
throw rte("Stream is already stopped");
}
PaError err = Pa_StopStream(_stream);
throwIfError(err);
}
Daq::StreamStatus PortAudioDaq::getStreamStatus() const
{
Daq::StreamStatus PortAudioDaq::getStreamStatus() const {
DEBUGTRACE_ENTER;
// Stores an error type and whether the
Daq::StreamStatus status;
// Copy over atomic flag.
status.errorType = _streamError;
// Check if stream is still running.
if (_stream)
{
if (Pa_IsStreamActive(_stream))
{
status.isRunning = true;
using StreamError = Daq::StreamStatus::StreamError;
Daq::StreamStatus::StreamError errortype = _streamError.load();
PaError err = Pa_IsStreamStopped(_stream);
if (err > 1) {
// 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;
} 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;
}
PortAudioDaq::~PortAudioDaq()
{
PortAudioDaq::~PortAudioDaq() {
PaError err;
if (_stream)
{
if (Pa_IsStreamActive(_stream))
{
stop();
}
err = Pa_CloseStream(_stream);
_stream = nullptr;
if (err != paNoError)
{
cerr << "Error closing PortAudio stream. Do not know what to do." << endl;
}
assert(_shouldPaTerminate);
assert(_stream);
if (Pa_IsStreamActive(_stream)) {
// Stop the stream first
stop();
}
if (_shouldPaTerminate)
{
err = Pa_Terminate();
if (err != paNoError)
{
cerr << "Error terminating PortAudio. Do not know what to do." << endl;
}
err = Pa_CloseStream(_stream);
_stream = nullptr;
if (err != paNoError) {
cerr << "Error closing PortAudio stream. 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,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo *timeInfo,
PaStreamCallbackFlags statusFlags)
{
DEBUGTRACE_ENTER;
PaStreamCallbackFlags statusFlags) {
// DEBUGTRACE_ENTER;
typedef Daq::StreamStatus::StreamError se;
if (statusFlags & paPrimingOutput)
{
if (statusFlags & paPrimingOutput) {
// Initial output buffers generated. So nothing with input yet
return paContinue;
}
if ((statusFlags & paInputUnderflow) || (statusFlags & paInputOverflow))
{
if ((statusFlags & paInputUnderflow) || (statusFlags & paInputOverflow)) {
_streamError = se::inputXRun;
return paAbort;
}
if ((statusFlags & paOutputUnderflow) || (statusFlags & paOutputOverflow))
{
if ((statusFlags & paOutputUnderflow) || (statusFlags & paOutputOverflow)) {
_streamError = se::outputXRun;
return paAbort;
}
if (framesPerBuffer != framesPerBlock())
{
if (framesPerBuffer != framesPerBlock()) {
cerr << "Logic error: expected a block size of: " << framesPerBlock()
<< endl;
_streamError = se::logicError;
@ -476,8 +475,7 @@ int PortAudioDaq::memberPaCallback(const void *inputBuffer, void *outputBuffer,
const auto &dtype_descr = dtypeDescr();
const auto dtype = dataType();
const us sw = dtype_descr.sw;
if (inputBuffer)
{
if (inputBuffer) {
assert(_incallback);
std::vector<byte_t *> ptrs;
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
/// noninterleaved, as specified in PortAudioDaq constructor.
for (us ch = ch_min; ch <= ch_max; ch++)
{
if (inchannel_config.at(ch).enabled)
{
for (us ch = ch_min; ch <= ch_max; ch++) {
if (inchannel_config.at(ch).enabled) {
byte_t *ch_ptr =
reinterpret_cast<byte_t **>(const_cast<void *>(inputBuffer))[ch];
ptrs.push_back(ch_ptr);
@ -504,8 +500,7 @@ int PortAudioDaq::memberPaCallback(const void *inputBuffer, void *outputBuffer,
_incallback(d);
}
if (outputBuffer)
{
if (outputBuffer) {
assert(_outcallback);
std::vector<byte_t *> ptrs;
ptrs.reserve(nenoutchannels);
@ -517,10 +512,8 @@ int PortAudioDaq::memberPaCallback(const void *inputBuffer, void *outputBuffer,
assert(ch_min < noutchannels);
assert(ch_max < noutchannels);
/// Only pass on the pointers of the channels we want
for (us ch = ch_min; ch <= ch_max; ch++)
{
if (outchannel_config.at(ch).enabled)
{
for (us ch = ch_min; ch <= ch_max; ch++) {
if (outchannel_config.at(ch).enabled) {
byte_t *ch_ptr = reinterpret_cast<byte_t **>(outputBuffer)[ch];
ptrs.push_back(ch_ptr);
}
@ -530,8 +523,7 @@ int PortAudioDaq::memberPaCallback(const void *inputBuffer, void *outputBuffer,
_outcallback(d);
// Copy over the buffer
us j = 0;
for (auto ptr : ptrs)
{
for (auto ptr : ptrs) {
d.copyToRaw(j, ptr);
j++;
}

View File

@ -57,6 +57,9 @@ class SLM:
level_ref_value: Reference value for computing the levels in dB
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
if xmin is None and fbdesigner is not None: