Merge branch 'develop' into windows_ready
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
commit
6353282e24
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -31,3 +31,6 @@
|
|||||||
[submodule "third_party/uldaq"]
|
[submodule "third_party/uldaq"]
|
||||||
path = third_party/uldaq
|
path = third_party/uldaq
|
||||||
url = https://github.com/asceenl/uldaq
|
url = https://github.com/asceenl/uldaq
|
||||||
|
[submodule "third_party/portaudio"]
|
||||||
|
path = third_party/portaudio
|
||||||
|
url = https://github.com/PortAudio/portaudio
|
||||||
|
@ -6,8 +6,26 @@ set(CMAKE_CXX_STANDARD 17)
|
|||||||
set(CMAKE_CXX_STANDARD_REQUIRED)
|
set(CMAKE_CXX_STANDARD_REQUIRED)
|
||||||
|
|
||||||
option(LASP_DOUBLE_PRECISION "Compile as double precision floating point" ON)
|
option(LASP_DOUBLE_PRECISION "Compile as double precision floating point" ON)
|
||||||
option(LASP_HAS_RTAUDIO "Compile with RtAudio Daq backend" ON)
|
|
||||||
option(LASP_HAS_ULDAQ "Compile with UlDaq backend" ON)
|
# Setting defaults for PortAudio and RtAudio backend, depending on Linux /
|
||||||
|
# Windows.
|
||||||
|
if(WIN32)
|
||||||
|
set(DEFAULT_RTAUDIO OFF)
|
||||||
|
set(DEFAULT_PORTAUDIO ON)
|
||||||
|
set(DEFAULT_ULDAQ OFF)
|
||||||
|
else()
|
||||||
|
set(DEFAULT_RTAUDIO ON)
|
||||||
|
set(DEFAULT_PORTAUDIO OFF)
|
||||||
|
set(DEFAULT_ULDAQ ON)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
|
||||||
|
option(LASP_HAS_RTAUDIO "Compile with RtAudio Daq backend" ${DEFAULT_RTAUDIO})
|
||||||
|
option(LASP_HAS_PORTAUDIO "Compile with PortAudio Daq backend" ${DEFAULT_PORTAUDIO})
|
||||||
|
if(LASP_HAS_PORTAUDIO AND LASP_HAS_RTAUDIO)
|
||||||
|
message(FATAL_ERROR "Either PortAudio or RtAudio can be selected as audio backend")
|
||||||
|
endif()
|
||||||
|
option(LASP_HAS_ULDAQ "Compile with UlDaq backend" ${DEFAULT_ULDAQ})
|
||||||
option(LASP_BUILD_TUNED "Tune build for current machine (Experimental / untested)" OFF)
|
option(LASP_BUILD_TUNED "Tune build for current machine (Experimental / untested)" OFF)
|
||||||
option(LASP_WITH_OPENMP "Use OpenMP parallelization (Experimental: crashes SHOULD BE EXPECTED)" OFF)
|
option(LASP_WITH_OPENMP "Use OpenMP parallelization (Experimental: crashes SHOULD BE EXPECTED)" OFF)
|
||||||
set(LASP_MAX_NFFT "33554432" CACHE STRING "Max FFT size")
|
set(LASP_MAX_NFFT "33554432" CACHE STRING "Max FFT size")
|
||||||
@ -90,7 +108,7 @@ endif()
|
|||||||
# ###################################### Compilation flags
|
# ###################################### Compilation flags
|
||||||
set(CMAKE_C_FLAGS_RELEASE "-O3 -flto -mfpmath=sse -march=x86-64 -mtune=native \
|
set(CMAKE_C_FLAGS_RELEASE "-O3 -flto -mfpmath=sse -march=x86-64 -mtune=native \
|
||||||
-fdata-sections -ffunction-sections -fomit-frame-pointer -finline-functions")
|
-fdata-sections -ffunction-sections -fomit-frame-pointer -finline-functions")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-type-limits")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-type-limits -Werror=return-type")
|
||||||
|
|
||||||
|
|
||||||
# ############################# End compilation flags
|
# ############################# End compilation flags
|
||||||
@ -99,8 +117,10 @@ include_directories(/usr/lib/python3.10/site-packages/numpy/core/include)
|
|||||||
# ####################################### End of user-adjustable variables section
|
# ####################################### End of user-adjustable variables section
|
||||||
include(OSSpecific)
|
include(OSSpecific)
|
||||||
include(rtaudio)
|
include(rtaudio)
|
||||||
|
include(portaudio)
|
||||||
include(uldaq)
|
include(uldaq)
|
||||||
#
|
#
|
||||||
|
add_definitions(-Dgsl_CONFIG_DEFAULTS_VERSION=1)
|
||||||
add_subdirectory(src/lasp)
|
add_subdirectory(src/lasp)
|
||||||
if(LASP_BUILD_CPP_TESTS)
|
if(LASP_BUILD_CPP_TESTS)
|
||||||
add_subdirectory(test)
|
add_subdirectory(test)
|
||||||
|
14
cmake/portaudio.cmake
Normal file
14
cmake/portaudio.cmake
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# ###################################### RtAudio
|
||||||
|
if(LASP_HAS_PORTAUDIO)
|
||||||
|
message("Building with Portaudio backend")
|
||||||
|
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_ALSA_DYNAMIC FALSE CACHE BOOL "Build static library of ALSA")
|
||||||
|
set(PA_BUILD_SHARED_LIBS FALSE CACHE BOOL "Build static library")
|
||||||
|
endif()
|
||||||
|
add_subdirectory(third_party/portaudio)
|
||||||
|
include_directories(third_party/portaudio/include)
|
||||||
|
link_directories(third_party/portaudio)
|
||||||
|
endif()
|
49
examples/test_input.py
Executable file
49
examples/test_input.py
Executable file
@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import lasp
|
||||||
|
# Get handle to stream manager
|
||||||
|
mgr = lasp.StreamMgr.getInstance()
|
||||||
|
import time
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
ds = mgr.getDeviceInfo()
|
||||||
|
# Search for a device
|
||||||
|
for i, d in enumerate(ds):
|
||||||
|
print(f'{i}: ' + d.device_name)
|
||||||
|
|
||||||
|
d = ds[0] # Create a configuration and enable some input channels
|
||||||
|
config = lasp.DaqConfiguration(d)
|
||||||
|
config.inchannel_config[0].enabled = True
|
||||||
|
config.inchannel_config[1].enabled = True
|
||||||
|
# Choose a different number of frames per block
|
||||||
|
config.framesPerBlockIndex = 2
|
||||||
|
|
||||||
|
# Start a stream with a configuration
|
||||||
|
mgr.startStream(config)
|
||||||
|
|
||||||
|
def reset_cb(daq):
|
||||||
|
print('Reset called')
|
||||||
|
|
||||||
|
def cb(data):
|
||||||
|
# Print something on callback
|
||||||
|
print(data.shape)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
# Attach the indata handler to the stream
|
||||||
|
#i = lasp.InDataHandler(mgr, cb, reset_cb)
|
||||||
|
|
||||||
|
ppm = lasp.PPMHandler(mgr)
|
||||||
|
#del ppm
|
||||||
|
del mgr
|
||||||
|
|
||||||
|
#del i
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
val, clip = ppm.getCurrentValue()
|
||||||
|
print(val)
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
#print(f'{val[0]} {val[1]}', end='')
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
# mgr.stopStream(lasp.StreamMgr.StreamType.input)
|
@ -1,5 +1,6 @@
|
|||||||
# src/lasp/device/CMakeLists.txt
|
# src/lasp/device/CMakeLists.txt
|
||||||
include_directories(uldaq)
|
include_directories(uldaq)
|
||||||
|
include_directories(portaudio)
|
||||||
|
|
||||||
add_library(lasp_device_lib OBJECT
|
add_library(lasp_device_lib OBJECT
|
||||||
lasp_daq.cpp
|
lasp_daq.cpp
|
||||||
@ -13,6 +14,7 @@ add_library(lasp_device_lib OBJECT
|
|||||||
uldaq/lasp_uldaq_impl.cpp
|
uldaq/lasp_uldaq_impl.cpp
|
||||||
uldaq/lasp_uldaq_bufhandler.cpp
|
uldaq/lasp_uldaq_bufhandler.cpp
|
||||||
uldaq/lasp_uldaq_common.cpp
|
uldaq/lasp_uldaq_common.cpp
|
||||||
|
portaudio/lasp_portaudiodaq.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
# Callback requires certain arguments that are not used by code. This disables
|
# Callback requires certain arguments that are not used by code. This disables
|
||||||
@ -31,6 +33,13 @@ endif()
|
|||||||
if(LASP_HAS_RTAUDIO)
|
if(LASP_HAS_RTAUDIO)
|
||||||
target_link_libraries(lasp_device_lib rtaudio)
|
target_link_libraries(lasp_device_lib rtaudio)
|
||||||
endif()
|
endif()
|
||||||
|
if(LASP_HAS_PORTAUDIO)
|
||||||
|
target_link_libraries(lasp_device_lib portaudio)
|
||||||
|
if(WIN32)
|
||||||
|
else()
|
||||||
|
target_link_libraries(lasp_device_lib asound)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
target_link_libraries(lasp_device_lib lasp_dsp_lib)
|
target_link_libraries(lasp_device_lib lasp_dsp_lib)
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
#include "debugtrace.hpp"
|
#include "debugtrace.hpp"
|
||||||
#include "lasp_daqconfig.h"
|
#include "lasp_daqconfig.h"
|
||||||
|
|
||||||
|
#include "lasp_config.h"
|
||||||
#include "lasp_daq.h"
|
#include "lasp_daq.h"
|
||||||
#if LASP_HAS_ULDAQ == 1
|
#if LASP_HAS_ULDAQ == 1
|
||||||
#include "lasp_uldaq.h"
|
#include "lasp_uldaq.h"
|
||||||
@ -9,6 +10,9 @@
|
|||||||
#if LASP_HAS_RTAUDIO == 1
|
#if LASP_HAS_RTAUDIO == 1
|
||||||
#include "lasp_rtaudiodaq.h"
|
#include "lasp_rtaudiodaq.h"
|
||||||
#endif
|
#endif
|
||||||
|
#if LASP_HAS_PORTAUDIO == 1
|
||||||
|
#include "lasp_portaudiodaq.h"
|
||||||
|
#endif
|
||||||
using rte = std::runtime_error;
|
using rte = std::runtime_error;
|
||||||
|
|
||||||
Daq::~Daq() { DEBUGTRACE_ENTER; }
|
Daq::~Daq() { DEBUGTRACE_ENTER; }
|
||||||
@ -27,6 +31,11 @@ std::unique_ptr<Daq> Daq::createDaq(const DeviceInfo &devinfo,
|
|||||||
if (devinfo.api.apicode == LASP_RTAUDIO_APICODE) {
|
if (devinfo.api.apicode == LASP_RTAUDIO_APICODE) {
|
||||||
return createRtAudioDevice(devinfo, config);
|
return createRtAudioDevice(devinfo, config);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
#if LASP_HAS_PORTAUDIO == 1
|
||||||
|
if (devinfo.api.apicode == LASP_PORTAUDIO_APICODE) {
|
||||||
|
return createPortAudioDevice(devinfo, config);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
throw rte(string("Unable to match Device API: ") + devinfo.api.apiname);
|
throw rte(string("Unable to match Device API: ") + devinfo.api.apiname);
|
||||||
}
|
}
|
||||||
|
@ -137,15 +137,19 @@ const DaqApi uldaqapi("UlDaq", 0);
|
|||||||
#include "RtAudio.h"
|
#include "RtAudio.h"
|
||||||
const us LASP_RTAUDIO_APICODE = 1;
|
const us LASP_RTAUDIO_APICODE = 1;
|
||||||
const DaqApi rtaudioAlsaApi("RtAudio Linux ALSA", 1, RtAudio::Api::LINUX_ALSA);
|
const DaqApi rtaudioAlsaApi("RtAudio Linux ALSA", 1, RtAudio::Api::LINUX_ALSA);
|
||||||
const DaqApi rtaudioPulseaudioApi("RtAudio Linux Pulseaudio", 1,
|
const DaqApi rtaudioPulseaudioApi("RtAudio Linux Pulseaudio", LASP_RTAUDIO_APICODE,
|
||||||
RtAudio::Api::LINUX_PULSE);
|
RtAudio::Api::LINUX_PULSE);
|
||||||
const DaqApi rtaudioWasapiApi("RtAudio Windows Wasapi", 1,
|
const DaqApi rtaudioWasapiApi("RtAudio Windows Wasapi", LASP_RTAUDIO_APICODE,
|
||||||
RtAudio::Api::WINDOWS_WASAPI);
|
RtAudio::Api::WINDOWS_WASAPI);
|
||||||
const DaqApi rtaudioDsApi("RtAudio Windows DirectSound", 1,
|
const DaqApi rtaudioDsApi("RtAudio Windows DirectSound", LASP_RTAUDIO_APICODE,
|
||||||
RtAudio::Api::WINDOWS_DS);
|
RtAudio::Api::WINDOWS_DS);
|
||||||
const DaqApi rtaudioAsioApi("RtAudio Windows ASIO", 1,
|
const DaqApi rtaudioAsioApi("RtAudio Windows ASIO", LASP_RTAUDIO_APICODE,
|
||||||
RtAudio::Api::WINDOWS_ASIO);
|
RtAudio::Api::WINDOWS_ASIO);
|
||||||
#endif
|
#endif
|
||||||
|
#if LASP_HAS_PORTAUDIO == 1
|
||||||
|
const us LASP_PORTAUDIO_APICODE = 2;
|
||||||
|
const DaqApi portaudioApi("PortAudio Linux ALSA", LASP_PORTAUDIO_APICODE, 0);
|
||||||
|
#endif
|
||||||
|
|
||||||
class DeviceInfo;
|
class DeviceInfo;
|
||||||
|
|
||||||
|
@ -10,6 +10,9 @@
|
|||||||
#if LASP_HAS_RTAUDIO == 1
|
#if LASP_HAS_RTAUDIO == 1
|
||||||
#include "lasp_rtaudiodaq.h"
|
#include "lasp_rtaudiodaq.h"
|
||||||
#endif
|
#endif
|
||||||
|
#if LASP_HAS_PORTAUDIO == 1
|
||||||
|
#include "lasp_portaudiodaq.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
DeviceInfoList DeviceInfo::getDeviceInfo() {
|
DeviceInfoList DeviceInfo::getDeviceInfo() {
|
||||||
@ -21,6 +24,9 @@ DeviceInfoList DeviceInfo::getDeviceInfo() {
|
|||||||
#if LASP_HAS_RTAUDIO == 1
|
#if LASP_HAS_RTAUDIO == 1
|
||||||
fillRtAudioDeviceInfo(devs);
|
fillRtAudioDeviceInfo(devs);
|
||||||
#endif
|
#endif
|
||||||
|
#if LASP_HAS_PORTAUDIO == 1
|
||||||
|
fillPortAudioDeviceInfo(devs);
|
||||||
|
#endif
|
||||||
|
|
||||||
return devs;
|
return devs;
|
||||||
}
|
}
|
||||||
|
@ -337,9 +337,9 @@ public:
|
|||||||
const auto &dtype_descr = dtypeDescr();
|
const auto &dtype_descr = dtypeDescr();
|
||||||
const auto dtype = dataType();
|
const auto dtype = dataType();
|
||||||
|
|
||||||
us neninchannels = this->neninchannels();
|
const us neninchannels = this->neninchannels();
|
||||||
us nenoutchannels = this->nenoutchannels();
|
const us nenoutchannels = this->nenoutchannels();
|
||||||
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;
|
||||||
|
@ -31,7 +31,7 @@ class StreamMgr {
|
|||||||
*/
|
*/
|
||||||
std::unique_ptr<Daq> _inputStream, _outputStream;
|
std::unique_ptr<Daq> _inputStream, _outputStream;
|
||||||
|
|
||||||
ThreadSafeThreadPool _pool;
|
GlobalThreadPool _pool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief All indata handlers are called when input data is available. Note
|
* @brief All indata handlers are called when input data is available. Note
|
||||||
|
445
src/lasp/device/portaudio/lasp_portaudiodaq.cpp
Normal file
445
src/lasp/device/portaudio/lasp_portaudiodaq.cpp
Normal file
@ -0,0 +1,445 @@
|
|||||||
|
/* #define DEBUGTRACE_ENABLED */
|
||||||
|
#include "debugtrace.hpp"
|
||||||
|
#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>
|
||||||
|
|
||||||
|
using rte = std::runtime_error;
|
||||||
|
using std::cerr;
|
||||||
|
using std::endl;
|
||||||
|
using std::string;
|
||||||
|
using std::to_string;
|
||||||
|
|
||||||
|
inline void throwIfError(PaError e) {
|
||||||
|
DEBUGTRACE_ENTER;
|
||||||
|
if (e != paNoError) {
|
||||||
|
throw rte(string("PortAudio backend error: ") + Pa_GetErrorText(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Device info, plus PortAudio stuff
|
||||||
|
*/
|
||||||
|
class OurPaDeviceInfo : public DeviceInfo {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Store instance to PaDeviceInfo.
|
||||||
|
*/
|
||||||
|
PaDeviceInfo _paDevInfo;
|
||||||
|
|
||||||
|
virtual std::unique_ptr<DeviceInfo> clone() const override final {
|
||||||
|
return std::make_unique<OurPaDeviceInfo>(*this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist) {
|
||||||
|
DEBUGTRACE_ENTER;
|
||||||
|
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);
|
||||||
|
|
||||||
|
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) {
|
||||||
|
throw rte("PortAudio could not find any devices");
|
||||||
|
}
|
||||||
|
for (us i = 0; i < (us)numDevices; i++) {
|
||||||
|
/* DEBUGTRACE_PRINT(i); */
|
||||||
|
|
||||||
|
const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(i);
|
||||||
|
if (!deviceInfo) {
|
||||||
|
throw rte("No device info struct returned");
|
||||||
|
}
|
||||||
|
OurPaDeviceInfo d;
|
||||||
|
d._paDevInfo = *deviceInfo;
|
||||||
|
d.api = portaudioApi;
|
||||||
|
d.device_name = deviceInfo->name;
|
||||||
|
|
||||||
|
d.availableDataTypes = {DataTypeDescriptor::DataType::dtype_int16,
|
||||||
|
DataTypeDescriptor::DataType::dtype_int32,
|
||||||
|
DataTypeDescriptor::DataType::dtype_fl32};
|
||||||
|
|
||||||
|
d.prefDataTypeIndex = 2;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
d.availableFramesPerBlock = {512, 1024, 2048, 4096, 8192};
|
||||||
|
d.prefFramesPerBlockIndex = 2;
|
||||||
|
|
||||||
|
d.availableInputRanges = {1.0};
|
||||||
|
|
||||||
|
d.ninchannels = deviceInfo->maxInputChannels;
|
||||||
|
d.noutchannels = deviceInfo->maxOutputChannels;
|
||||||
|
|
||||||
|
devinfolist.push_back(std::make_unique<OurPaDeviceInfo>(d));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
catch (rte &e) {
|
||||||
|
cerr << "PortAudio backend error: " << e.what() << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Forward declaration of raw callback. Calls into
|
||||||
|
* PortAudioDaq->memberPaCallback. Undocumented parameters are specified
|
||||||
|
* in memberPaCallback
|
||||||
|
*
|
||||||
|
* @param inputBuffer
|
||||||
|
* @param outputBuffer
|
||||||
|
* @param framesPerBuffer
|
||||||
|
* @param timeInfo
|
||||||
|
* @param statusFlags
|
||||||
|
* @param userData Pointer to PortAudioDaq* instance.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
static int rawPaCallback(const void *inputBuffer, void *outputBuffer,
|
||||||
|
unsigned long framesPerBuffer,
|
||||||
|
const PaStreamCallbackTimeInfo *timeInfo,
|
||||||
|
PaStreamCallbackFlags statusFlags, void *userData);
|
||||||
|
|
||||||
|
class PortAudioDaq : public Daq {
|
||||||
|
bool _shouldPaTerminate = false;
|
||||||
|
PaStream *_stream = nullptr;
|
||||||
|
std::atomic<StreamStatus::StreamError> _streamError =
|
||||||
|
StreamStatus::StreamError::noError;
|
||||||
|
InDaqCallback _incallback;
|
||||||
|
OutDaqCallback _outcallback;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PortAudioDaq(const OurPaDeviceInfo &devinfo_gen,
|
||||||
|
const DaqConfiguration &config);
|
||||||
|
|
||||||
|
void start(InDaqCallback inCallback,
|
||||||
|
OutDaqCallback outCallback) override final;
|
||||||
|
void stop() override final;
|
||||||
|
|
||||||
|
StreamStatus getStreamStatus() const override final;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Member va
|
||||||
|
*
|
||||||
|
* @param inputBuffer
|
||||||
|
* @param outputBuffer
|
||||||
|
* @param framesPerBuffer
|
||||||
|
* @param timeInfo
|
||||||
|
* @param statusFlags
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
int memberPaCallback(const void *inputBuffer, void *outputBuffer,
|
||||||
|
unsigned long framesPerBuffer,
|
||||||
|
const PaStreamCallbackTimeInfo *timeInfo,
|
||||||
|
PaStreamCallbackFlags statusFlags);
|
||||||
|
~PortAudioDaq();
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<Daq> createPortAudioDevice(const DeviceInfo &devinfo,
|
||||||
|
const DaqConfiguration &config) {
|
||||||
|
|
||||||
|
const OurPaDeviceInfo *_info =
|
||||||
|
dynamic_cast<const OurPaDeviceInfo *>(&devinfo);
|
||||||
|
if (_info == nullptr) {
|
||||||
|
throw rte("BUG: Could not cast DeviceInfo to OurPaDeviceInfo");
|
||||||
|
}
|
||||||
|
return std::make_unique<PortAudioDaq>(*_info, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int rawPaCallback(const void *inputBuffer, void *outputBuffer,
|
||||||
|
unsigned long framesPerBuffer,
|
||||||
|
const PaStreamCallbackTimeInfo *timeInfo,
|
||||||
|
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) {
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 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++) {
|
||||||
|
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._paDevInfo.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));
|
||||||
|
}
|
||||||
|
|
||||||
|
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)neninchannels(),
|
||||||
|
.sampleFormat = format,
|
||||||
|
.suggestedLatency = framesPerBlock() / samplerate(),
|
||||||
|
.hostApiSpecificStreamInfo = nullptr}));
|
||||||
|
}
|
||||||
|
if (nenoutchannels() > 0) {
|
||||||
|
outstreamParams = std::make_unique<PaStreamParameters>(
|
||||||
|
PaStreamParameters({.device = devindex,
|
||||||
|
.channelCount = (int)nenoutchannels(),
|
||||||
|
.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) {
|
||||||
|
DEBUGTRACE_ENTER;
|
||||||
|
assert(_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 (neninchannels() > 0) {
|
||||||
|
if (!inCallback) {
|
||||||
|
throw rte(
|
||||||
|
|
||||||
|
"Input callback given, but stream does not provide input data");
|
||||||
|
}
|
||||||
|
|
||||||
|
_incallback = inCallback;
|
||||||
|
}
|
||||||
|
if (nenoutchannels() > 0) {
|
||||||
|
if (!outCallback) {
|
||||||
|
throw rte(
|
||||||
|
"Output callback given, but stream does not provide output data");
|
||||||
|
}
|
||||||
|
_outcallback = outCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
PaError err = Pa_StartStream(_stream);
|
||||||
|
throwIfError(err);
|
||||||
|
}
|
||||||
|
void PortAudioDaq::stop() {
|
||||||
|
DEBUGTRACE_ENTER;
|
||||||
|
assert(_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 status;
|
||||||
|
// Copy over atomic flag.
|
||||||
|
status.errorType = _streamError;
|
||||||
|
// Check if stream is still running.
|
||||||
|
if (_stream) {
|
||||||
|
if (Pa_IsStreamActive(_stream)) {
|
||||||
|
status.isRunning = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_shouldPaTerminate) {
|
||||||
|
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;
|
||||||
|
typedef Daq::StreamStatus::StreamError se;
|
||||||
|
if (statusFlags & paPrimingOutput) {
|
||||||
|
// Initial output buffers generated. So nothing with input yet
|
||||||
|
return paContinue;
|
||||||
|
}
|
||||||
|
if ((statusFlags & paInputUnderflow) || (statusFlags & paInputOverflow)) {
|
||||||
|
_streamError = se::inputXRun;
|
||||||
|
return paAbort;
|
||||||
|
}
|
||||||
|
if ((statusFlags & paOutputUnderflow) || (statusFlags & paOutputOverflow)) {
|
||||||
|
_streamError = se::outputXRun;
|
||||||
|
return paAbort;
|
||||||
|
}
|
||||||
|
if (framesPerBuffer != framesPerBlock()) {
|
||||||
|
cerr << "Logic error: expected a block size of: " << framesPerBlock()
|
||||||
|
<< endl;
|
||||||
|
_streamError = se::logicError;
|
||||||
|
return paAbort;
|
||||||
|
}
|
||||||
|
|
||||||
|
const us neninchannels = this->neninchannels();
|
||||||
|
const us nenoutchannels = this->nenoutchannels();
|
||||||
|
const auto &dtype_descr = dtypeDescr();
|
||||||
|
const auto dtype = dataType();
|
||||||
|
const us sw = dtype_descr.sw;
|
||||||
|
if (inputBuffer) {
|
||||||
|
assert(_incallback);
|
||||||
|
std::vector<byte_t *> ptrs;
|
||||||
|
ptrs.reserve(neninchannels);
|
||||||
|
|
||||||
|
const us ch_min = getLowestEnabledInChannel();
|
||||||
|
const us ch_max = getHighestEnabledInChannel();
|
||||||
|
assert(ch_min < ninchannels);
|
||||||
|
assert(ch_max < ninchannels);
|
||||||
|
|
||||||
|
/// 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) {
|
||||||
|
byte_t *ch_ptr =
|
||||||
|
reinterpret_cast<byte_t **>(const_cast<void *>(inputBuffer))[ch];
|
||||||
|
ptrs.push_back(ch_ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DaqData d{framesPerBuffer, neninchannels, dtype};
|
||||||
|
d.copyInFromRaw(ptrs);
|
||||||
|
|
||||||
|
_incallback(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outputBuffer) {
|
||||||
|
assert(_outcallback);
|
||||||
|
std::vector<byte_t *> ptrs;
|
||||||
|
ptrs.reserve(nenoutchannels);
|
||||||
|
|
||||||
|
/* outCallback */
|
||||||
|
|
||||||
|
const us ch_min = getLowestEnabledOutChannel();
|
||||||
|
const us ch_max = getHighestEnabledOutChannel();
|
||||||
|
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) {
|
||||||
|
byte_t *ch_ptr = reinterpret_cast<byte_t **>(outputBuffer)[ch];
|
||||||
|
ptrs.push_back(ch_ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DaqData d{framesPerBuffer, nenoutchannels, dtype};
|
||||||
|
|
||||||
|
_outcallback(d);
|
||||||
|
// Copy over the buffer
|
||||||
|
us j = 0;
|
||||||
|
for (auto ptr : ptrs) {
|
||||||
|
d.copyToRaw(j, ptr);
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return paContinue;
|
||||||
|
}
|
||||||
|
#endif
|
35
src/lasp/device/portaudio/lasp_portaudiodaq.h
Normal file
35
src/lasp/device/portaudio/lasp_portaudiodaq.h
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "lasp_daq.h"
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
/** \addtogroup device
|
||||||
|
* @{
|
||||||
|
* \defgroup portaudio PortAudio backend
|
||||||
|
* This code is used to interface with the PortAudio cross-platform audio
|
||||||
|
* interface.
|
||||||
|
*
|
||||||
|
* \addtogroup portaudio
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Method called from Daq::createDaq.
|
||||||
|
*
|
||||||
|
* @param devinfo Device info
|
||||||
|
* @param config DAQ Configuration settings
|
||||||
|
*
|
||||||
|
* @return Pointer to Daq instance. Throws Runtime errors on error.
|
||||||
|
*/
|
||||||
|
std::unique_ptr<Daq> createPortAudioDevice(const DeviceInfo& devinfo,
|
||||||
|
const DaqConfiguration& config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Append PortAudio backend devices to the list
|
||||||
|
*
|
||||||
|
* @param devinfolist List to append to
|
||||||
|
*/
|
||||||
|
void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist);
|
||||||
|
|
||||||
|
/** @} */
|
||||||
|
/** @} */
|
@ -61,7 +61,7 @@ public:
|
|||||||
class BiquadBank : public Filter {
|
class BiquadBank : public Filter {
|
||||||
std::vector<SeriesBiquad> _filters;
|
std::vector<SeriesBiquad> _filters;
|
||||||
vd _gains;
|
vd _gains;
|
||||||
ThreadSafeThreadPool _pool;
|
GlobalThreadPool _pool;
|
||||||
mutable std::mutex _mtx;
|
mutable std::mutex _mtx;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
* channel. A channel is the result of a filtered signal
|
* channel. A channel is the result of a filtered signal
|
||||||
*/
|
*/
|
||||||
class SLM {
|
class SLM {
|
||||||
ThreadSafeThreadPool _pool;
|
GlobalThreadPool _pool;
|
||||||
/**
|
/**
|
||||||
* @brief A, C or Z weighting, depending on the pre-filter installed.
|
* @brief A, C or Z weighting, depending on the pre-filter installed.
|
||||||
*/
|
*/
|
||||||
|
@ -12,14 +12,15 @@
|
|||||||
std::weak_ptr<BS::thread_pool> _global_weak_pool;
|
std::weak_ptr<BS::thread_pool> _global_weak_pool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Static storage for the mutex.
|
* @brief Global mutex, used to restrict the pool creation to a single thread
|
||||||
|
* at once.
|
||||||
*/
|
*/
|
||||||
std::mutex ThreadSafeThreadPool::_mtx;
|
std::mutex _mtx;
|
||||||
|
|
||||||
using Lck = std::scoped_lock<std::mutex>;
|
using Lck = std::scoped_lock<std::mutex>;
|
||||||
using rte = std::runtime_error;
|
using rte = std::runtime_error;
|
||||||
|
|
||||||
ThreadSafeThreadPool::ThreadSafeThreadPool() {
|
GlobalThreadPool::GlobalThreadPool() {
|
||||||
DEBUGTRACE_ENTER;
|
DEBUGTRACE_ENTER;
|
||||||
Lck lck(_mtx);
|
Lck lck(_mtx);
|
||||||
/// See if we can get it from the global ptr. If not, time to allocate it.
|
/// See if we can get it from the global ptr. If not, time to allocate it.
|
||||||
|
@ -7,29 +7,20 @@
|
|||||||
* safely spawn threads also from other threads. Only wraps a submit() and
|
* safely spawn threads also from other threads. Only wraps a submit() and
|
||||||
* push_task for now.
|
* push_task for now.
|
||||||
*/
|
*/
|
||||||
class ThreadSafeThreadPool {
|
class GlobalThreadPool {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Shared access to the thread pool.
|
* @brief Shared access to the thread pool.
|
||||||
*/
|
*/
|
||||||
std::shared_ptr<BS::thread_pool> _pool;
|
std::shared_ptr<BS::thread_pool> _pool;
|
||||||
/**
|
|
||||||
* @brief Global mutex, used to restrict pool access to a single thread at
|
|
||||||
* once.
|
|
||||||
*/
|
|
||||||
static std::mutex _mtx;
|
|
||||||
|
|
||||||
using Lck = std::scoped_lock<std::mutex>;
|
|
||||||
ThreadSafeThreadPool(const ThreadSafeThreadPool&) = delete;
|
|
||||||
ThreadSafeThreadPool &
|
|
||||||
operator=(const ThreadSafeThreadPool&) = delete;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief Instantiate handle to the thread pool.
|
* @brief Instantiate handle to the thread pool.
|
||||||
*/
|
*/
|
||||||
ThreadSafeThreadPool();
|
GlobalThreadPool();
|
||||||
|
GlobalThreadPool(const GlobalThreadPool &) = default;
|
||||||
|
GlobalThreadPool &operator=(const GlobalThreadPool &) = default;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Wrapper around BS::thread_pool::submit(...)
|
* @brief Wrapper around BS::thread_pool::submit(...)
|
||||||
@ -38,8 +29,6 @@ public:
|
|||||||
typename F, typename... A,
|
typename F, typename... A,
|
||||||
typename R = std::invoke_result_t<std::decay_t<F>, std::decay_t<A>...>>
|
typename R = std::invoke_result_t<std::decay_t<F>, std::decay_t<A>...>>
|
||||||
[[nodiscard]] std::future<R> submit(F &&task, A &&...args) {
|
[[nodiscard]] std::future<R> submit(F &&task, A &&...args) {
|
||||||
/// Lock access to pool
|
|
||||||
Lck lck(_mtx);
|
|
||||||
|
|
||||||
return _pool->submit(task, args...);
|
return _pool->submit(task, args...);
|
||||||
}
|
}
|
||||||
@ -47,9 +36,6 @@ public:
|
|||||||
* @brief Wrapper around BS::thread_pool::push_task(...)
|
* @brief Wrapper around BS::thread_pool::push_task(...)
|
||||||
*/
|
*/
|
||||||
template <typename F, typename... A> void push_task(F &&task, A &&...args) {
|
template <typename F, typename... A> void push_task(F &&task, A &&...args) {
|
||||||
/// Lock access to pool
|
|
||||||
Lck lck(_mtx);
|
|
||||||
_pool->push_task(task, args...);
|
_pool->push_task(task, args...);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ class ThreadedInDataHandlerBase {
|
|||||||
std::atomic<bool> _thread_running{false};
|
std::atomic<bool> _thread_running{false};
|
||||||
std::atomic<bool> _thread_can_safely_run{false};
|
std::atomic<bool> _thread_can_safely_run{false};
|
||||||
|
|
||||||
ThreadSafeThreadPool _pool;
|
GlobalThreadPool _pool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Function pointer that is called when new DaqData arrives.
|
* @brief Function pointer that is called when new DaqData arrives.
|
||||||
|
@ -31,6 +31,7 @@ const int LASP_VERSION_MINOR = @CMAKE_PROJECT_VERSION_MINOR@;
|
|||||||
|
|
||||||
#cmakedefine LASP_FFT_BACKEND @LASP_FFT_BACKEND@
|
#cmakedefine LASP_FFT_BACKEND @LASP_FFT_BACKEND@
|
||||||
#cmakedefine01 LASP_HAS_RTAUDIO
|
#cmakedefine01 LASP_HAS_RTAUDIO
|
||||||
|
#cmakedefine01 LASP_HAS_PORTAUDIO
|
||||||
#cmakedefine01 LASP_HAS_ULDAQ
|
#cmakedefine01 LASP_HAS_ULDAQ
|
||||||
#cmakedefine01 LASP_DOUBLE_PRECISION
|
#cmakedefine01 LASP_DOUBLE_PRECISION
|
||||||
#cmakedefine01 LASP_USE_BLAS
|
#cmakedefine01 LASP_USE_BLAS
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include <pybind11/stl.h>
|
#include <pybind11/stl.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <iostream>
|
||||||
#include "lasp_deviceinfo.h"
|
#include "lasp_deviceinfo.h"
|
||||||
|
|
||||||
using std::cerr;
|
using std::cerr;
|
||||||
|
1
third_party/portaudio
vendored
Submodule
1
third_party/portaudio
vendored
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit cb8d3dcbc6fa74c67f3e236be89b12d5630da141
|
Loading…
Reference in New Issue
Block a user