From 08010e56dd57a703c5ba79a08ad5570128dce744 Mon Sep 17 00:00:00 2001 From: "J.A. de Jong - Redu-Sone B.V., ASCEE V.O.F." Date: Sat, 20 Jan 2024 11:52:16 +0100 Subject: [PATCH] From now on build default LASP with Portaudio backend. Also on Linux. Code cleanup of Portaudio glue code --- .../device/portaudio/lasp_portaudiodaq.cpp | 168 ++++++++++++------ pyproject.toml | 24 ++- third_party/rtaudio | 2 +- 3 files changed, 130 insertions(+), 64 deletions(-) diff --git a/cpp_src/device/portaudio/lasp_portaudiodaq.cpp b/cpp_src/device/portaudio/lasp_portaudiodaq.cpp index da0cc9d..fd1acd0 100644 --- a/cpp_src/device/portaudio/lasp_portaudiodaq.cpp +++ b/cpp_src/device/portaudio/lasp_portaudiodaq.cpp @@ -15,9 +15,11 @@ 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)); } } @@ -25,21 +27,25 @@ inline void throwIfError(PaError e) { /** * @brief Device info, plus PortAudio stuff */ -class OurPaDeviceInfo : public DeviceInfo { +class OurPaDeviceInfo : public DeviceInfo +{ public: /** * @brief Store instance to PaDeviceInfo. */ PaDeviceInfo _paDevInfo; - virtual std::unique_ptr clone() const override final { + virtual std::unique_ptr clone() const override final + { return std::make_unique(*this); } }; -void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) { +void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) +{ DEBUGTRACE_ENTER; - try { + try + { PaError err = Pa_Initialize(); /// PortAudio says that Pa_Terminate() should not be called whenever there @@ -47,24 +53,27 @@ void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) { /// of PortAudio show. throwIfError(err); - 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 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); */ const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(i); - if (!deviceInfo) { + if (!deviceInfo) + { throw rte("No device info struct returned"); } OurPaDeviceInfo d; @@ -80,7 +89,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; @@ -100,7 +109,8 @@ void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) { } } - catch (rte &e) { + catch (rte &e) + { cerr << "PortAudio backend error: " << e.what() << std::endl; return; } @@ -125,7 +135,8 @@ static int rawPaCallback(const void *inputBuffer, void *outputBuffer, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData); -class PortAudioDaq : public Daq { +class PortAudioDaq : public Daq +{ bool _shouldPaTerminate = false; PaStream *_stream = nullptr; std::atomic _streamError = @@ -162,11 +173,13 @@ public: }; std::unique_ptr createPortAudioDevice(const DeviceInfo &devinfo, - const DaqConfiguration &config) { + const DaqConfiguration &config) +{ const OurPaDeviceInfo *_info = dynamic_cast(&devinfo); - if (_info == nullptr) { + if (_info == nullptr) + { throw rte("BUG: Could not cast DeviceInfo to OurPaDeviceInfo"); } return std::make_unique(*_info, config); @@ -175,14 +188,16 @@ std::unique_ptr 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(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(); @@ -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 // the index, as this is required in the PaStreamParameters struct 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; const PaDeviceInfo *info = Pa_GetDeviceInfo(i); - if (!info) { + if (!info) + { throw rte("No device structure returned from PortAudio"); } 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->defaultSampleRate == devinfo_gen._paDevInfo.defaultSampleRate; - if (ok) { + if (ok) + { devindex = i; } } - if (devindex < 0) { + if (devindex < 0) + { 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(); // Sample format is bit flag PaSampleFormat format = paNonInterleaved; - switch (dtype) { + switch (dtype) + { case Dtype::dtype_fl32: DEBUGTRACE_PRINT("Datatype float32"); format |= paFloat32; @@ -252,18 +272,20 @@ PortAudioDaq::PortAudioDaq(const OurPaDeviceInfo &devinfo_gen, std::unique_ptr instreamParams; std::unique_ptr outstreamParams; - if (neninchannels() > 0) { + if (neninchannels() > 0) + { instreamParams = std::make_unique( PaStreamParameters({.device = devindex, - .channelCount = (int)neninchannels(), + .channelCount = (int)getHighestEnabledInChannel() + 1, .sampleFormat = format, .suggestedLatency = framesPerBlock() / samplerate(), .hostApiSpecificStreamInfo = nullptr})); } - if (nenoutchannels() > 0) { + if (nenoutchannels() > 0) + { outstreamParams = std::make_unique( PaStreamParameters({.device = devindex, - .channelCount = (int)nenoutchannels(), + .channelCount = (int)getHighestEnabledOutChannel() + 1, .sampleFormat = format, .suggestedLatency = framesPerBlock() / samplerate(), .hostApiSpecificStreamInfo = nullptr})); @@ -284,21 +306,26 @@ PortAudioDaq::PortAudioDaq(const OurPaDeviceInfo &devinfo_gen, 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) { + if (inCallback && outCallback) + { throw rte("Either input or output stream possible for RtAudio. " "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"); @@ -306,8 +333,10 @@ 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"); } @@ -317,46 +346,57 @@ 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)) + { throw rte("Stream is already stopped"); } PaError err = Pa_StopStream(_stream); throwIfError(err); } -Daq::StreamStatus PortAudioDaq::getStreamStatus() const { +Daq::StreamStatus PortAudioDaq::getStreamStatus() const +{ Daq::StreamStatus status; // Copy over atomic flag. status.errorType = _streamError; // Check if stream is still running. - if (_stream) { - if (Pa_IsStreamActive(_stream)) { + if (_stream) + { + if (Pa_IsStreamActive(_stream)) + { status.isRunning = true; } } return status; } -PortAudioDaq::~PortAudioDaq() { +PortAudioDaq::~PortAudioDaq() +{ PaError err; - if (_stream) { - if (Pa_IsStreamActive(_stream)) { + if (_stream) + { + if (Pa_IsStreamActive(_stream)) + { stop(); } err = Pa_CloseStream(_stream); _stream = nullptr; - if (err != paNoError) { + if (err != paNoError) + { cerr << "Error closing PortAudio stream. Do not know what to do." << endl; } assert(_shouldPaTerminate); } - if (_shouldPaTerminate) { + if (_shouldPaTerminate) + { err = Pa_Terminate(); - if (err != paNoError) { + if (err != paNoError) + { 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, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, - PaStreamCallbackFlags statusFlags) { + 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; @@ -392,7 +437,8 @@ 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 ptrs; 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 /// 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(const_cast(inputBuffer))[ch]; ptrs.push_back(ch_ptr); @@ -417,7 +465,8 @@ int PortAudioDaq::memberPaCallback(const void *inputBuffer, void *outputBuffer, _incallback(d); } - if (outputBuffer) { + if (outputBuffer) + { assert(_outcallback); std::vector ptrs; ptrs.reserve(nenoutchannels); @@ -429,8 +478,10 @@ 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(outputBuffer)[ch]; ptrs.push_back(ch_ptr); } @@ -440,7 +491,8 @@ 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++; } diff --git a/pyproject.toml b/pyproject.toml index 78e48af..1235212 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,23 +22,37 @@ classifiers = [ ] urls = { "Documentation" = "https://lasp.ascee.nl" } -dependencies = ["scipy", "numpy", "matplotlib>=3.7.2", "appdirs", - "dataclasses_json", "h5py"] +dependencies = [ + "scipy", + "numpy", + "matplotlib>=3.7.2", + "appdirs", + "dataclasses_json", + "h5py", +] [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" [tool.py-build-cmake.module] # Where to find the Python module to package directory = "python_src" [tool.py-build-cmake.sdist] # What to include in source distributions -include = ["CMakeLists.txt", "cmake", "cpp_src", "python_src", "img", "scripts", - "third_party"] +include = [ + "CMakeLists.txt", + "cmake", + "cpp_src", + "python_src", + "img", + "scripts", + "third_party", +] [tool.py-build-cmake.cmake] # How to build the CMake project build_type = "Release" source_path = "." +options = { "LASP_HAS_PORTAUDIO" = "ON", "LASP_HAS_RTAUDIO" = "OFF" } build_args = ["-j12"] install_components = ["python_modules"] diff --git a/third_party/rtaudio b/third_party/rtaudio index b4f0490..46b01b5 160000 --- a/third_party/rtaudio +++ b/third_party/rtaudio @@ -1 +1 @@ -Subproject commit b4f04903312e0e0efffbe77655172e0f060dc085 +Subproject commit 46b01b5b134f33d8ddc3dab76829d4b1350e0522