Compare commits

...

6 Commits

Author SHA1 Message Date
08010e56dd From now on build default LASP with Portaudio backend. Also on Linux. Code cleanup of Portaudio glue code
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 3m3s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped
2024-01-20 11:52:16 +01:00
292a9d5938 Merge branch 'RtAudioV6' into develop 2024-01-19 12:37:16 +01:00
373dcfb60f Bugfixes (that could potentially segfault) in PortAudio backend.
All checks were successful
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 2m54s
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Successful in 2s
2024-01-19 12:35:56 +01:00
e8408ab53d Updated to RtAudio V6 2024-01-19 12:32:45 +01:00
0d152f6c14 Maded API changes to match RtAudio V6 2024-01-19 12:32:03 +01:00
46bef007ca Added muZ series impedance reference as measurementtype
Some checks failed
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Has been cancelled
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been cancelled
2024-01-15 16:41:29 +01:00
6 changed files with 261 additions and 135 deletions

View File

@ -34,7 +34,7 @@ if(LASP_HAS_RTAUDIO)
target_link_libraries(lasp_device_lib rtaudio) target_link_libraries(lasp_device_lib rtaudio)
endif() endif()
if(LASP_HAS_PORTAUDIO) if(LASP_HAS_PORTAUDIO)
target_link_libraries(lasp_device_lib portaudio) target_link_libraries(lasp_device_lib PortAudio)
if(WIN32) if(WIN32)
else() else()
target_link_libraries(lasp_device_lib asound) target_link_libraries(lasp_device_lib asound)

View File

@ -17,37 +17,46 @@ using rte = std::runtime_error;
using std::vector; using std::vector;
using lck = std::scoped_lock<std::mutex>; using lck = std::scoped_lock<std::mutex>;
class RtAudioDeviceInfo : public DeviceInfo { const unsigned RTAUDIO_MAX_CHANNELS = 8;
class RtAudioDeviceInfo : public DeviceInfo
{
public: public:
/** /**
* @brief Specific for the device (Sub-API). Important for the RtAudio * @brief Specific for the device (Sub-API). Important for the RtAudio
* backend, as RtAudio is able to handle different API's. * backend, as RtAudio is able to handle different API's.
*/ */
int _api_devindex; int ID; // Copy of RtAudio::DeviceInfo::ID
virtual std::unique_ptr<DeviceInfo> clone() const override { virtual std::unique_ptr<DeviceInfo> clone() const override
{
return std::make_unique<DeviceInfo>(*this); return std::make_unique<DeviceInfo>(*this);
} }
}; };
void fillRtAudioDeviceInfo(DeviceInfoList &devinfolist) { void fillRtAudioDeviceInfo(DeviceInfoList &devinfolist)
{
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
vector<RtAudio::Api> apis; vector<RtAudio::Api> apis;
RtAudio::getCompiledApi(apis); RtAudio::getCompiledApi(apis);
for (auto api : apis) { for (auto api : apis)
{
RtAudio rtaudio(api); RtAudio rtaudio(api);
us count = rtaudio.getDeviceCount(); const us count = rtaudio.getDeviceCount();
for (us devno = 0; devno < count; devno++) {
const auto ids = rtaudio.getDeviceIds();
for (us i = 0; i < count; i++)
{
us id = ids.at(i);
RtAudio::DeviceInfo devinfo = rtaudio.getDeviceInfo(id);
RtAudio::DeviceInfo devinfo = rtaudio.getDeviceInfo(devno);
if (!devinfo.probed) {
// Device capabilities not successfully probed. Continue to next
continue;
}
// "Our device info struct" // "Our device info struct"
RtAudioDeviceInfo d; RtAudioDeviceInfo d;
switch (api) { switch (api)
{
case RtAudio::LINUX_ALSA: case RtAudio::LINUX_ALSA:
d.api = rtaudioAlsaApi; d.api = rtaudioAlsaApi;
break; break;
@ -70,43 +79,49 @@ void fillRtAudioDeviceInfo(DeviceInfoList &devinfolist) {
} }
d.device_name = devinfo.name; d.device_name = devinfo.name;
d._api_devindex = devno; d.ID = id;
/// When 48k is available we overwrite the default sample rate with the 48 /// When 48k is available we overwrite the default sample rate with the 48
/// kHz value, which is our preffered rate, /// kHz value, which is our preffered rate,
bool rate_48k_found = false; bool rate_48k_found = false;
for (us j = 0; j < devinfo.sampleRates.size(); j++) { for (us j = 0; j < devinfo.sampleRates.size(); j++)
{
us rate_int = devinfo.sampleRates[j]; us rate_int = devinfo.sampleRates[j];
d.availableSampleRates.push_back((double)rate_int); d.availableSampleRates.push_back((double)rate_int);
if (!rate_48k_found) { if (!rate_48k_found)
{
if (devinfo.preferredSampleRate == rate_int) { if (devinfo.preferredSampleRate == rate_int)
{
d.prefSampleRateIndex = j; d.prefSampleRateIndex = j;
} }
if (rate_int == 48000) { if (rate_int == 48000)
{
d.prefSampleRateIndex = j; d.prefSampleRateIndex = j;
rate_48k_found = true; rate_48k_found = true;
} }
} }
} }
d.noutchannels = devinfo.outputChannels; d.noutchannels = std::min(devinfo.outputChannels, RTAUDIO_MAX_CHANNELS);
d.ninchannels = devinfo.inputChannels; d.ninchannels = std::min(devinfo.inputChannels, RTAUDIO_MAX_CHANNELS);
d.availableInputRanges = {1.0}; d.availableInputRanges = {1.0};
d.availableOutputRanges = {1.0}; d.availableOutputRanges = {1.0};
RtAudioFormat formats = devinfo.nativeFormats; RtAudioFormat formats = devinfo.nativeFormats;
if (formats & RTAUDIO_SINT8) { if (formats & RTAUDIO_SINT8)
{
d.availableDataTypes.push_back( d.availableDataTypes.push_back(
DataTypeDescriptor::DataType::dtype_int8); DataTypeDescriptor::DataType::dtype_int8);
} }
if (formats & RTAUDIO_SINT16) { if (formats & RTAUDIO_SINT16)
{
d.availableDataTypes.push_back( d.availableDataTypes.push_back(
DataTypeDescriptor::DataType::dtype_int16); DataTypeDescriptor::DataType::dtype_int16);
} }
@ -114,15 +129,18 @@ void fillRtAudioDeviceInfo(DeviceInfoList &devinfolist) {
/* d.availableDataTypes.push_back(DataTypeDescriptor::DataType::dtype_int24); /* d.availableDataTypes.push_back(DataTypeDescriptor::DataType::dtype_int24);
*/ */
/* } */ /* } */
if (formats & RTAUDIO_SINT32) { if (formats & RTAUDIO_SINT32)
{
d.availableDataTypes.push_back( d.availableDataTypes.push_back(
DataTypeDescriptor::DataType::dtype_fl32); DataTypeDescriptor::DataType::dtype_fl32);
} }
if (formats & RTAUDIO_FLOAT64) { if (formats & RTAUDIO_FLOAT64)
{
d.availableDataTypes.push_back( d.availableDataTypes.push_back(
DataTypeDescriptor::DataType::dtype_fl64); DataTypeDescriptor::DataType::dtype_fl64);
} }
if (d.availableDataTypes.size() == 0) { if (d.availableDataTypes.size() == 0)
{
std::cerr << "RtAudio: No data types found in device!" << endl; std::cerr << "RtAudio: No data types found in device!" << endl;
} }
@ -140,9 +158,8 @@ static int mycallback(void *outputBuffer, void *inputBuffer,
unsigned int nFrames, double streamTime, unsigned int nFrames, double streamTime,
RtAudioStreamStatus status, void *userData); RtAudioStreamStatus status, void *userData);
static void myerrorcallback(RtAudioError::Type, const string &errorText); class RtAudioDaq : public Daq
{
class RtAudioDaq : public Daq {
RtAudio rtaudio; RtAudio rtaudio;
const us nFramesPerBlock; const us nFramesPerBlock;
@ -182,10 +199,11 @@ public:
/// 0. For now, our fix is to shift out the channels we want, and let /// 0. For now, our fix is to shift out the channels we want, and let
/// RtAudio pass on all channels. /// RtAudio pass on all channels.
inParams->firstChannel = 0; inParams->firstChannel = 0;
inParams->nChannels = devinfo_gen.ninchannels; inParams->nChannels = devinfo.ninchannels;
inParams->deviceId = devinfo._api_devindex; inParams->deviceId = devinfo.ID;
}
} else { else
{
outParams = std::make_unique<RtAudio::StreamParameters>(); outParams = std::make_unique<RtAudio::StreamParameters>();
@ -193,8 +211,8 @@ public:
/// 0. For now, our fix is to shift out the channels we want, and let /// 0. For now, our fix is to shift out the channels we want, and let
/// RtAudio pass on all channels. /// RtAudio pass on all channels.
outParams->firstChannel = 0; outParams->firstChannel = 0;
outParams->nChannels = devinfo_gen.noutchannels; outParams->nChannels = devinfo.noutchannels;
outParams->deviceId = devinfo._api_devindex; outParams->deviceId = devinfo.ID;
} }
RtAudio::StreamOptions streamoptions; RtAudio::StreamOptions streamoptions;
@ -207,7 +225,8 @@ public:
RtAudioFormat format; RtAudioFormat format;
using Dtype = DataTypeDescriptor::DataType; using Dtype = DataTypeDescriptor::DataType;
const Dtype dtype = dataType(); const Dtype dtype = dataType();
switch (dtype) { switch (dtype)
{
case Dtype::dtype_fl32: case Dtype::dtype_fl32:
DEBUGTRACE_PRINT("Datatype float32"); DEBUGTRACE_PRINT("Datatype float32");
format = RTAUDIO_FLOAT32; format = RTAUDIO_FLOAT32;
@ -238,36 +257,46 @@ public:
unsigned int nFramesPerBlock_copy = nFramesPerBlock; unsigned int nFramesPerBlock_copy = nFramesPerBlock;
// Final step: open the stream. // Final step: open the stream.
rtaudio.openStream(outParams.get(), inParams.get(), format, RtAudioErrorType err = rtaudio.openStream(outParams.get(), inParams.get(), format,
static_cast<us>(samplerate()), &nFramesPerBlock_copy, static_cast<us>(samplerate()), &nFramesPerBlock_copy,
mycallback, (void *)this, &streamoptions, mycallback, (void *)this, &streamoptions);
&myerrorcallback); if (err != RTAUDIO_NO_ERROR)
{
throw std::runtime_error(string("Error opening stream: ") + rtaudio.getErrorText());
}
if (nFramesPerBlock_copy != nFramesPerBlock) { if (nFramesPerBlock_copy != nFramesPerBlock)
{
throw rte(string("Got different number of frames per block back from RtAudio " throw rte(string("Got different number of frames per block back from RtAudio "
"backend: ") + std::to_string(nFramesPerBlock_copy) + ". I do not know what to do."); "backend: ") +
std::to_string(nFramesPerBlock_copy) + ". I do not know what to do.");
} }
} }
virtual void start(InDaqCallback inCallback, virtual void start(InDaqCallback inCallback,
OutDaqCallback outCallback) override final { OutDaqCallback outCallback) override final
{
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
assert(!monitorOutput); assert(!monitorOutput);
if (getStreamStatus().runningOK()) { if (getStreamStatus().runningOK())
{
throw rte("Stream already running"); throw rte("Stream already running");
} }
// Logical XOR // Logical XOR
if (inCallback && outCallback) { if (inCallback && outCallback)
{
throw rte("Either input or output stream possible for RtAudio. " throw rte("Either input or output stream possible for RtAudio. "
"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");
@ -275,8 +304,10 @@ public:
_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");
} }
@ -284,7 +315,11 @@ public:
} }
// Start the stream. Throws on error. // Start the stream. Throws on error.
rtaudio.startStream(); const auto err = rtaudio.startStream();
if (err != RTAUDIO_NO_ERROR)
{
throw std::runtime_error(string("Error starting stream: ") + rtaudio.getErrorText());
}
// If we are here, we are running without errors. // If we are here, we are running without errors.
StreamStatus status; StreamStatus status;
@ -294,10 +329,15 @@ public:
StreamStatus getStreamStatus() const override final { return _streamStatus; } StreamStatus getStreamStatus() const override final { return _streamStatus; }
void stop() override final { void stop() override final
{
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
if (getStreamStatus().runningOK()) { if (getStreamStatus().runningOK())
rtaudio.stopStream(); {
const auto err = rtaudio.stopStream();
if(err != RTAUDIO_NO_ERROR) {
std::cerr << "Error occured while stopping the stream: " << rtaudio.getErrorText() << endl;
}
} }
StreamStatus s = _streamStatus; StreamStatus s = _streamStatus;
s.isRunning = false; s.isRunning = false;
@ -306,14 +346,16 @@ public:
} }
int streamCallback(void *outputBuffer, void *inputBuffer, int streamCallback(void *outputBuffer, void *inputBuffer,
unsigned int nFrames, RtAudioStreamStatus status) { unsigned int nFrames, RtAudioStreamStatus status)
{
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
using se = StreamStatus::StreamError; using se = StreamStatus::StreamError;
int rval = 0; int rval = 0;
auto stopWithError = [&](se e) { auto stopWithError = [&](se e)
{
DEBUGTRACE_PRINT("stopWithError"); DEBUGTRACE_PRINT("stopWithError");
StreamStatus stat = _streamStatus; StreamStatus stat = _streamStatus;
stat.errorType = e; stat.errorType = e;
@ -322,7 +364,8 @@ public:
rval = 1; rval = 1;
}; };
switch (status) { switch (status)
{
case RTAUDIO_INPUT_OVERFLOW: case RTAUDIO_INPUT_OVERFLOW:
stopWithError(se::inputXRun); stopWithError(se::inputXRun);
return 1; return 1;
@ -341,14 +384,16 @@ public:
const us neninchannels = this->neninchannels(); const us neninchannels = this->neninchannels();
const us nenoutchannels = this->nenoutchannels(); const us nenoutchannels = this->nenoutchannels();
const us sw = dtype_descr.sw; const us sw = dtype_descr.sw;
if (nFrames != nFramesPerBlock) { if (nFrames != nFramesPerBlock)
{
cerr << "RtAudio backend error: nFrames does not match block size!" cerr << "RtAudio backend error: nFrames does not match block size!"
<< endl; << endl;
stopWithError(se::logicError); stopWithError(se::logicError);
return 1; return 1;
} }
if (inputBuffer) { if (inputBuffer)
{
assert(_incallback); assert(_incallback);
std::vector<byte_t *> ptrs; std::vector<byte_t *> ptrs;
ptrs.reserve(neninchannels); ptrs.reserve(neninchannels);
@ -359,8 +404,10 @@ public:
assert(ch_max < ninchannels); assert(ch_max < ninchannels);
/// 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 (inchannel_config.at(ch).enabled) { {
if (inchannel_config.at(ch).enabled)
{
byte_t *ptr = byte_t *ptr =
static_cast<byte_t *>(inputBuffer) + sw * ch * nFramesPerBlock; static_cast<byte_t *>(inputBuffer) + sw * ch * nFramesPerBlock;
ptrs.push_back(ptr); ptrs.push_back(ptr);
@ -369,10 +416,11 @@ public:
DaqData d{nFramesPerBlock, neninchannels, dtype}; DaqData d{nFramesPerBlock, neninchannels, dtype};
d.copyInFromRaw(ptrs); d.copyInFromRaw(ptrs);
_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);
@ -384,8 +432,10 @@ public:
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)
{
ptrs.push_back(static_cast<byte_t *>(outputBuffer) + ptrs.push_back(static_cast<byte_t *>(outputBuffer) +
sw * ch * nFramesPerBlock); sw * ch * nFramesPerBlock);
} }
@ -395,7 +445,8 @@ public:
_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++;
} }
@ -411,17 +462,16 @@ public:
}; };
std::unique_ptr<Daq> createRtAudioDevice(const DeviceInfo &devinfo, std::unique_ptr<Daq> createRtAudioDevice(const DeviceInfo &devinfo,
const DaqConfiguration &config) { const DaqConfiguration &config)
{
return std::make_unique<RtAudioDaq>(devinfo, config); return std::make_unique<RtAudioDaq>(devinfo, config);
} }
void myerrorcallback(RtAudioError::Type, const string &errorText) {
cerr << "RtAudio backend stream error: " << errorText << endl;
}
int mycallback( int mycallback(
void *outputBuffer, void *inputBuffer, unsigned int nFrames, void *outputBuffer, void *inputBuffer, unsigned int nFrames,
__attribute__((unused)) double streamTime, // Not used parameter streamTime __attribute__((unused)) double streamTime, // Not used parameter streamTime
RtAudioStreamStatus status, void *userData) { RtAudioStreamStatus status, void *userData)
{
return static_cast<RtAudioDaq *>(userData)->streamCallback( return static_cast<RtAudioDaq *>(userData)->streamCallback(
outputBuffer, inputBuffer, nFrames, status); outputBuffer, inputBuffer, nFrames, status);

View File

@ -15,9 +15,11 @@ 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));
} }
} }
@ -25,21 +27,25 @@ 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);
} }
}; };
void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) { void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist)
{
DEBUGTRACE_ENTER; DEBUGTRACE_ENTER;
try { try
{
PaError err = Pa_Initialize(); PaError err = Pa_Initialize();
/// PortAudio says that Pa_Terminate() should not be called whenever there /// PortAudio says that Pa_Terminate() should not be called whenever there
@ -47,28 +53,33 @@ void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) {
/// of PortAudio show. /// of PortAudio show.
throwIfError(err); throwIfError(err);
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 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); */
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;
d._paDevInfo = *deviceInfo; d._paDevInfo = *deviceInfo;
// We store the name in d.device_name
d._paDevInfo.name = nullptr;
d.api = portaudioApi; d.api = portaudioApi;
d.device_name = deviceInfo->name; d.device_name = deviceInfo->name;
@ -78,7 +89,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;
@ -87,6 +98,9 @@ void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) {
d.prefFramesPerBlockIndex = 2; d.prefFramesPerBlockIndex = 2;
d.availableInputRanges = {1.0}; d.availableInputRanges = {1.0};
// d.prefInputRangeIndex = 0; // Constructor-defined
d.availableOutputRanges = {1.0};
// d.prefOutputRangeIndex = 0; // Constructor-defined
d.ninchannels = deviceInfo->maxInputChannels; d.ninchannels = deviceInfo->maxInputChannels;
d.noutchannels = deviceInfo->maxOutputChannels; d.noutchannels = deviceInfo->maxOutputChannels;
@ -95,7 +109,8 @@ void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) {
} }
} }
catch (rte &e) { catch (rte &e)
{
cerr << "PortAudio backend error: " << e.what() << std::endl; cerr << "PortAudio backend error: " << e.what() << std::endl;
return; return;
} }
@ -120,7 +135,8 @@ 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; bool _shouldPaTerminate = false;
PaStream *_stream = nullptr; PaStream *_stream = nullptr;
std::atomic<StreamStatus::StreamError> _streamError = std::atomic<StreamStatus::StreamError> _streamError =
@ -157,11 +173,13 @@ public:
}; };
std::unique_ptr<Daq> createPortAudioDevice(const DeviceInfo &devinfo, std::unique_ptr<Daq> createPortAudioDevice(const DeviceInfo &devinfo,
const DaqConfiguration &config) { const DaqConfiguration &config)
{
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);
@ -170,14 +188,16 @@ 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(); PaError err = Pa_Initialize();
@ -193,23 +213,28 @@ PortAudioDaq::PortAudioDaq(const OurPaDeviceInfo &devinfo_gen,
// Going to find the device in the list. If its there, we have to retrieve // Going to find the device in the list. If its there, we have to retrieve
// the index, as this is required in the PaStreamParameters struct // the index, as this is required in the PaStreamParameters struct
int devindex = -1; int devindex = -1;
for (int i = 0; i < Pa_GetDeviceCount(); i++) { for (int i = 0; i < Pa_GetDeviceCount(); i++)
{
// DEBUGTRACE_PRINT(i);
bool ok = true; 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._paDevInfo.name; ok &= string(info->name) == devinfo_gen.device_name;
ok &= info->hostApi == devinfo_gen._paDevInfo.hostApi; ok &= info->hostApi == devinfo_gen._paDevInfo.hostApi;
ok &= info->maxInputChannels == devinfo_gen._paDevInfo.maxInputChannels; ok &= info->maxInputChannels == devinfo_gen._paDevInfo.maxInputChannels;
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));
} }
@ -217,7 +242,8 @@ 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;
@ -246,18 +272,20 @@ 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>( instreamParams = std::make_unique<PaStreamParameters>(
PaStreamParameters({.device = devindex, PaStreamParameters({.device = devindex,
.channelCount = (int)neninchannels(), .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>( outstreamParams = std::make_unique<PaStreamParameters>(
PaStreamParameters({.device = devindex, PaStreamParameters({.device = devindex,
.channelCount = (int)nenoutchannels(), .channelCount = (int)getHighestEnabledOutChannel() + 1,
.sampleFormat = format, .sampleFormat = format,
.suggestedLatency = framesPerBlock() / samplerate(), .suggestedLatency = framesPerBlock() / samplerate(),
.hostApiSpecificStreamInfo = nullptr})); .hostApiSpecificStreamInfo = nullptr}));
@ -278,21 +306,26 @@ PortAudioDaq::PortAudioDaq(const OurPaDeviceInfo &devinfo_gen,
throwIfError(err); 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("Either input or output stream possible for RtAudio. " throw rte("Either input or output stream possible for RtAudio. "
"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");
@ -300,8 +333,10 @@ void PortAudioDaq::start(InDaqCallback inCallback, OutDaqCallback outCallback) {
_incallback = inCallback; _incallback = inCallback;
} }
if (nenoutchannels() > 0) { if (nenoutchannels() > 0)
if (!outCallback) { {
if (!outCallback)
{
throw rte( throw rte(
"Output callback given, but stream does not provide output data"); "Output callback given, but stream does not provide output data");
} }
@ -311,46 +346,57 @@ 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))
{
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
{
Daq::StreamStatus status; Daq::StreamStatus status;
// Copy over atomic flag. // Copy over atomic flag.
status.errorType = _streamError; status.errorType = _streamError;
// Check if stream is still running. // Check if stream is still running.
if (_stream) { if (_stream)
if (Pa_IsStreamActive(_stream)) { {
if (Pa_IsStreamActive(_stream))
{
status.isRunning = true; status.isRunning = true;
} }
} }
return status; return status;
} }
PortAudioDaq::~PortAudioDaq() { PortAudioDaq::~PortAudioDaq()
{
PaError err; PaError err;
if (_stream) { if (_stream)
if (Pa_IsStreamActive(_stream)) { {
if (Pa_IsStreamActive(_stream))
{
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); assert(_shouldPaTerminate);
} }
if (_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;
} }
} }
@ -358,23 +404,28 @@ PortAudioDaq::~PortAudioDaq() {
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;
@ -386,7 +437,8 @@ int PortAudioDaq::memberPaCallback(const void *inputBuffer, void *outputBuffer,
const auto &dtype_descr = dtypeDescr(); const auto &dtype_descr = dtypeDescr();
const auto dtype = dataType(); const auto dtype = dataType();
const us sw = dtype_descr.sw; const us sw = dtype_descr.sw;
if (inputBuffer) { if (inputBuffer)
{
assert(_incallback); assert(_incallback);
std::vector<byte_t *> ptrs; std::vector<byte_t *> ptrs;
ptrs.reserve(neninchannels); ptrs.reserve(neninchannels);
@ -398,8 +450,10 @@ int PortAudioDaq::memberPaCallback(const void *inputBuffer, void *outputBuffer,
/// Only pass on the pointers of the channels we want. inputBuffer is /// Only pass on the pointers of the channels we want. inputBuffer is
/// noninterleaved, as specified in PortAudioDaq constructor. /// noninterleaved, as specified in PortAudioDaq constructor.
for (us ch = ch_min; ch <= ch_max; ch++) { for (us ch = ch_min; ch <= ch_max; ch++)
if (inchannel_config.at(ch).enabled) { {
if (inchannel_config.at(ch).enabled)
{
byte_t *ch_ptr = byte_t *ch_ptr =
reinterpret_cast<byte_t **>(const_cast<void *>(inputBuffer))[ch]; reinterpret_cast<byte_t **>(const_cast<void *>(inputBuffer))[ch];
ptrs.push_back(ch_ptr); ptrs.push_back(ch_ptr);
@ -411,7 +465,8 @@ int PortAudioDaq::memberPaCallback(const void *inputBuffer, void *outputBuffer,
_incallback(d); _incallback(d);
} }
if (outputBuffer) { if (outputBuffer)
{
assert(_outcallback); assert(_outcallback);
std::vector<byte_t *> ptrs; std::vector<byte_t *> ptrs;
ptrs.reserve(nenoutchannels); ptrs.reserve(nenoutchannels);
@ -423,8 +478,10 @@ int PortAudioDaq::memberPaCallback(const void *inputBuffer, void *outputBuffer,
assert(ch_min < noutchannels); assert(ch_min < noutchannels);
assert(ch_max < noutchannels); assert(ch_max < noutchannels);
/// Only pass on the pointers of the channels we want /// Only pass on the pointers of the channels we want
for (us ch = ch_min; ch <= ch_max; ch++) { for (us ch = ch_min; ch <= ch_max; ch++)
if (outchannel_config.at(ch).enabled) { {
if (outchannel_config.at(ch).enabled)
{
byte_t *ch_ptr = reinterpret_cast<byte_t **>(outputBuffer)[ch]; byte_t *ch_ptr = reinterpret_cast<byte_t **>(outputBuffer)[ch];
ptrs.push_back(ch_ptr); ptrs.push_back(ch_ptr);
} }
@ -434,7 +491,8 @@ int PortAudioDaq::memberPaCallback(const void *inputBuffer, void *outputBuffer,
_outcallback(d); _outcallback(d);
// Copy over the buffer // Copy over the buffer
us j = 0; us j = 0;
for (auto ptr : ptrs) { for (auto ptr : ptrs)
{
d.copyToRaw(j, ptr); d.copyToRaw(j, ptr);
j++; j++;
} }

View File

@ -22,23 +22,37 @@ classifiers = [
] ]
urls = { "Documentation" = "https://lasp.ascee.nl" } urls = { "Documentation" = "https://lasp.ascee.nl" }
dependencies = ["scipy", "numpy", "matplotlib>=3.7.2", "appdirs", dependencies = [
"dataclasses_json", "h5py"] "scipy",
"numpy",
"matplotlib>=3.7.2",
"appdirs",
"dataclasses_json",
"h5py",
]
[build-system] # How pip and other frontends should build this project [build-system] # How pip and other frontends should build this project
requires = ["py-build-cmake~=0.1.8", "pybind11" ] requires = ["py-build-cmake~=0.1.8", "pybind11"]
build-backend = "py_build_cmake.build" build-backend = "py_build_cmake.build"
[tool.py-build-cmake.module] # Where to find the Python module to package [tool.py-build-cmake.module] # Where to find the Python module to package
directory = "python_src" directory = "python_src"
[tool.py-build-cmake.sdist] # What to include in source distributions [tool.py-build-cmake.sdist] # What to include in source distributions
include = ["CMakeLists.txt", "cmake", "cpp_src", "python_src", "img", "scripts", include = [
"third_party"] "CMakeLists.txt",
"cmake",
"cpp_src",
"python_src",
"img",
"scripts",
"third_party",
]
[tool.py-build-cmake.cmake] # How to build the CMake project [tool.py-build-cmake.cmake] # How to build the CMake project
build_type = "Release" build_type = "Release"
source_path = "." source_path = "."
options = { "LASP_HAS_PORTAUDIO" = "ON", "LASP_HAS_RTAUDIO" = "OFF" }
build_args = ["-j12"] build_args = ["-j12"]
install_components = ["python_modules"] install_components = ["python_modules"]

View File

@ -91,6 +91,9 @@ class MeasurementType(Enum):
# Measurement serves as impedance tube calibration (long tube case) # Measurement serves as impedance tube calibration (long tube case)
muZCalLong = 1 << 3 muZCalLong = 1 << 3
# Series impedance reference
muZSeriesImpedanceRef = 1 << 4
def __str__(self): def __str__(self):
match self: match self:
case MeasurementType.NotSpecific: return '-' case MeasurementType.NotSpecific: return '-'
@ -98,6 +101,7 @@ class MeasurementType(Enum):
case MeasurementType.CALGeneral: return 'General calibration' case MeasurementType.CALGeneral: return 'General calibration'
case MeasurementType.muZCalShort: return 'ASCEE μZ short length calibration' case MeasurementType.muZCalShort: return 'ASCEE μZ short length calibration'
case MeasurementType.muZCalLong: return 'ASCEE μZ long length calibration' case MeasurementType.muZCalLong: return 'ASCEE μZ long length calibration'
case MeasurementType.muZSeriesImpedanceRef: return 'ASCEE μZ series impedance empty reference'
case _: raise ValueError("Not a MeasurementType") case _: raise ValueError("Not a MeasurementType")

@ -1 +1 @@
Subproject commit cb8d3dcbc6fa74c67f3e236be89b12d5630da141 Subproject commit daaf637f6f9fce670031221abfd7dfde92e5cce3