From now on build default LASP with Portaudio backend. Also on Linux. Code cleanup of Portaudio glue code
Building, testing and releasing LASP if it has a tag / Build-Test-Ubuntu (push) Successful in 3m3s Details
Building, testing and releasing LASP if it has a tag / Release-Ubuntu (push) Has been skipped Details

This commit is contained in:
Anne de Jong 2024-01-20 11:52:16 +01:00
parent 292a9d5938
commit 08010e56dd
3 changed files with 130 additions and 64 deletions

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,24 +53,27 @@ 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;
@ -80,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;
@ -100,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;
} }
@ -125,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 =
@ -162,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);
@ -175,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();
@ -198,11 +213,13 @@ 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); // DEBUGTRACE_PRINT(i);
bool ok = true; bool ok = true;
const PaDeviceInfo *info = Pa_GetDeviceInfo(i); const PaDeviceInfo *info = Pa_GetDeviceInfo(i);
if (!info) { if (!info)
{
throw rte("No device structure returned from PortAudio"); throw rte("No device structure returned from PortAudio");
} }
ok &= string(info->name) == devinfo_gen.device_name; ok &= string(info->name) == devinfo_gen.device_name;
@ -211,11 +228,13 @@ PortAudioDaq::PortAudioDaq(const OurPaDeviceInfo &devinfo_gen,
ok &= info->maxOutputChannels == devinfo_gen._paDevInfo.maxOutputChannels; ok &= info->maxOutputChannels == devinfo_gen._paDevInfo.maxOutputChannels;
ok &= info->defaultSampleRate == devinfo_gen._paDevInfo.defaultSampleRate; ok &= info->defaultSampleRate == devinfo_gen._paDevInfo.defaultSampleRate;
if (ok) { if (ok)
{
devindex = i; devindex = i;
} }
} }
if (devindex < 0) { if (devindex < 0)
{
throw rte(string("Device not found: ") + string(devinfo_gen.device_name)); throw rte(string("Device not found: ") + string(devinfo_gen.device_name));
} }
@ -223,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;
@ -252,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}));
@ -284,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");
@ -306,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");
} }
@ -317,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;
} }
} }
@ -364,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;
@ -392,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);
@ -404,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);
@ -417,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);
@ -429,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);
} }
@ -440,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"]

2
third_party/rtaudio vendored

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