2#include "debugtrace.hpp"
3#include "lasp_config.h"
5#if LASP_HAS_PORTAUDIO == 1
8#include <gsl-lite/gsl-lite.hpp>
12using rte = std::runtime_error;
18inline void throwIfError(PaError e) {
21 throw rte(
string(
"PortAudio backend error: ") + Pa_GetErrorText(e));
33 PaDeviceInfo _paDevInfo;
35 virtual std::unique_ptr<DeviceInfo>
clone() const override final {
36 return std::make_unique<OurPaDeviceInfo>(*
this);
44 PaError err = Pa_Initialize();
50 auto fin = gsl::finally([&err] {
51 DEBUGTRACE_PRINT(
"Terminating PortAudio instance");
53 if (err != paNoError) {
54 cerr <<
"Error terminating PortAudio. Do not know what to do." << endl;
59 const int numDevices = Pa_GetDeviceCount();
61 throw rte(
"PortAudio could not find any devices");
63 for (
us i = 0; i < (
us)numDevices; i++) {
66 const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(i);
68 throw rte(
"No device info struct returned");
71 d._paDevInfo = *deviceInfo;
73 d.device_name = deviceInfo->name;
79 d.prefDataTypeIndex = 2;
81 d.availableSampleRates = {8000.0, 9600.0, 11025.0, 12000.0, 16000.0,
82 22050.0, 24000.0, 32000.0, 44100.0, 48000.0,
83 88200.0, 96000.0, 192000.0};
84 d.prefSampleRateIndex = 9;
86 d.availableFramesPerBlock = {512, 1024, 2048, 4096, 8192};
87 d.prefFramesPerBlockIndex = 2;
89 d.availableInputRanges = {1.0};
91 d.ninchannels = deviceInfo->maxInputChannels;
92 d.noutchannels = deviceInfo->maxOutputChannels;
94 devinfolist.push_back(std::make_unique<OurPaDeviceInfo>(d));
99 cerr <<
"PortAudio backend error: " << e.what() << std::endl;
118static int rawPaCallback(
const void *inputBuffer,
void *outputBuffer,
119 unsigned long framesPerBuffer,
120 const PaStreamCallbackTimeInfo *timeInfo,
121 PaStreamCallbackFlags statusFlags,
void *userData);
123class PortAudioDaq :
public Daq {
124 bool _shouldPaTerminate =
false;
125 PaStream *_stream =
nullptr;
126 std::atomic<StreamStatus::StreamError> _streamError =
132 PortAudioDaq(
const OurPaDeviceInfo &devinfo_gen,
137 void stop() override final;
139 StreamStatus getStreamStatus() const override final;
152 int memberPaCallback(const
void *inputBuffer,
void *outputBuffer,
153 unsigned long framesPerBuffer,
154 const PaStreamCallbackTimeInfo *timeInfo,
155 PaStreamCallbackFlags statusFlags);
162 const OurPaDeviceInfo *_info =
163 dynamic_cast<const OurPaDeviceInfo *
>(&devinfo);
164 if (_info ==
nullptr) {
165 throw rte(
"BUG: Could not cast DeviceInfo to OurPaDeviceInfo");
167 return std::make_unique<PortAudioDaq>(*_info, config);
170static int rawPaCallback(
const void *inputBuffer,
void *outputBuffer,
171 unsigned long framesPerBuffer,
172 const PaStreamCallbackTimeInfo *timeInfo,
173 PaStreamCallbackFlags statusFlags,
void *userData) {
174 return static_cast<PortAudioDaq *
>(userData)->memberPaCallback(
175 inputBuffer, outputBuffer, framesPerBuffer, timeInfo, statusFlags);
178PortAudioDaq::PortAudioDaq(
const OurPaDeviceInfo &devinfo_gen,
180 :
Daq(devinfo_gen, config) {
183 PaError err = Pa_Initialize();
191 _shouldPaTerminate =
true;
196 for (
int i = 0; i < Pa_GetDeviceCount(); i++) {
198 const PaDeviceInfo *info = Pa_GetDeviceInfo(i);
200 throw rte(
"No device structure returned from PortAudio");
202 ok &= string(info->name) == devinfo_gen._paDevInfo.name;
203 ok &= info->hostApi == devinfo_gen._paDevInfo.hostApi;
204 ok &= info->maxInputChannels == devinfo_gen._paDevInfo.maxInputChannels;
205 ok &= info->maxOutputChannels == devinfo_gen._paDevInfo.maxOutputChannels;
206 ok &= info->defaultSampleRate == devinfo_gen._paDevInfo.defaultSampleRate;
213 throw rte(
string(
"Device not found: ") +
string(devinfo_gen.device_name));
217 const Dtype dtype = dataType();
219 PaSampleFormat format = paNonInterleaved;
221 case Dtype::dtype_fl32:
222 DEBUGTRACE_PRINT(
"Datatype float32");
225 case Dtype::dtype_fl64:
226 DEBUGTRACE_PRINT(
"Datatype float64");
227 throw rte(
"Invalid data type specified for DAQ stream.");
229 case Dtype::dtype_int8:
230 DEBUGTRACE_PRINT(
"Datatype int8");
233 case Dtype::dtype_int16:
234 DEBUGTRACE_PRINT(
"Datatype int16");
237 case Dtype::dtype_int32:
238 DEBUGTRACE_PRINT(
"Datatype int32");
242 throw rte(
"Invalid data type specified for DAQ stream.");
246 std::unique_ptr<PaStreamParameters> instreamParams;
247 std::unique_ptr<PaStreamParameters> outstreamParams;
249 if (neninchannels() > 0) {
250 instreamParams = std::make_unique<PaStreamParameters>(
251 PaStreamParameters({.device = devindex,
252 .channelCount = (int)neninchannels(),
253 .sampleFormat = format,
254 .suggestedLatency = framesPerBlock() / samplerate(),
255 .hostApiSpecificStreamInfo =
nullptr}));
257 if (nenoutchannels() > 0) {
258 outstreamParams = std::make_unique<PaStreamParameters>(
259 PaStreamParameters({.device = devindex,
260 .channelCount = (int)nenoutchannels(),
261 .sampleFormat = format,
262 .suggestedLatency = framesPerBlock() / samplerate(),
263 .hostApiSpecificStreamInfo =
nullptr}));
267 err = Pa_IsFormatSupported(instreamParams.get(), outstreamParams.get(),
271 err = Pa_OpenStream(&_stream,
272 instreamParams.get(),
273 outstreamParams.get(),
277 rawPaCallback,
this);
284 if (Pa_IsStreamActive(_stream)) {
285 throw rte(
"Stream is already running");
289 if (inCallback && outCallback) {
290 throw rte(
"Either input or output stream possible for RtAudio. "
291 "Stream duplex mode not provided.");
294 if (neninchannels() > 0) {
298 "Input callback given, but stream does not provide input data");
301 _incallback = inCallback;
303 if (nenoutchannels() > 0) {
306 "Output callback given, but stream does not provide output data");
308 _outcallback = outCallback;
311 PaError err = Pa_StartStream(_stream);
314void PortAudioDaq::stop() {
317 if (Pa_IsStreamStopped(_stream)) {
318 throw rte(
"Stream is already stopped");
320 PaError err = Pa_StopStream(_stream);
329 if (Pa_IsStreamActive(_stream)) {
336PortAudioDaq::~PortAudioDaq() {
339 if (Pa_IsStreamActive(_stream)) {
343 err = Pa_CloseStream(_stream);
345 if (err != paNoError) {
346 cerr <<
"Error closing PortAudio stream. Do not know what to do." << endl;
348 assert(_shouldPaTerminate);
351 if (_shouldPaTerminate) {
352 err = Pa_Terminate();
353 if (err != paNoError) {
354 cerr <<
"Error terminating PortAudio. Do not know what to do." << endl;
358int PortAudioDaq::memberPaCallback(
const void *inputBuffer,
void *outputBuffer,
359 unsigned long framesPerBuffer,
360 const PaStreamCallbackTimeInfo *timeInfo,
361 PaStreamCallbackFlags statusFlags) {
365 if (statusFlags & paPrimingOutput) {
369 if ((statusFlags & paInputUnderflow) || (statusFlags & paInputOverflow)) {
370 _streamError = se::inputXRun;
373 if ((statusFlags & paOutputUnderflow) || (statusFlags & paOutputOverflow)) {
374 _streamError = se::outputXRun;
377 if (framesPerBuffer != framesPerBlock()) {
378 cerr <<
"Logic error: expected a block size of: " << framesPerBlock()
380 _streamError = se::logicError;
384 const us neninchannels = this->neninchannels();
385 const us nenoutchannels = this->nenoutchannels();
386 const auto &dtype_descr = dtypeDescr();
387 const auto dtype = dataType();
388 const us sw = dtype_descr.sw;
391 std::vector<byte_t *> ptrs;
392 ptrs.reserve(neninchannels);
394 const us ch_min = getLowestEnabledInChannel();
395 const us ch_max = getHighestEnabledInChannel();
396 assert(ch_min < ninchannels);
397 assert(ch_max < ninchannels);
401 for (
us ch = ch_min; ch <= ch_max; ch++) {
402 if (inchannel_config.at(ch).enabled) {
404 reinterpret_cast<byte_t **
>(
const_cast<void *
>(inputBuffer))[ch];
405 ptrs.push_back(ch_ptr);
408 DaqData d{framesPerBuffer, neninchannels, dtype};
409 d.copyInFromRaw(ptrs);
415 assert(_outcallback);
416 std::vector<byte_t *> ptrs;
417 ptrs.reserve(nenoutchannels);
421 const us ch_min = getLowestEnabledOutChannel();
422 const us ch_max = getHighestEnabledOutChannel();
423 assert(ch_min < noutchannels);
424 assert(ch_max < noutchannels);
426 for (
us ch = ch_min; ch <= ch_max; ch++) {
427 if (outchannel_config.at(ch).enabled) {
428 byte_t *ch_ptr =
reinterpret_cast<byte_t **
>(outputBuffer)[ch];
429 ptrs.push_back(ch_ptr);
432 DaqData d{framesPerBuffer, nenoutchannels, dtype};
437 for (
auto ptr : ptrs) {
Information regarding a stream.
Configuration of a DAQ device.
Data coming from / going to DAQ. Non-interleaved format, which means data in buffer is ordered by cha...
Base cass for all DAQ (Data Acquisition) interfaces. A DAQ can be a custom device,...
virtual void start(InDaqCallback inCallback, OutDaqCallback outCallback)=0
Start the Daq.
virtual void stop()=0
Stop the Daq device. Throws an exception if the device is not running at the time this method is call...
DataType
Basic data types coming from a DAQ that we can deal with. The naming will be self-explainging.
Structure containing device info parameters.
virtual std::unique_ptr< DeviceInfo > clone() const
Clone a device info.
std::vector< std::unique_ptr< DeviceInfo > > DeviceInfoList
std::function< void(DaqData &)> OutDaqCallback
std::function< void(const DaqData &)> InDaqCallback
std::unique_ptr< Daq > createPortAudioDevice(const DeviceInfo &devinfo, const DaqConfiguration &config)
Method called from Daq::createDaq.
void fillPortAudioDeviceInfo(DeviceInfoList &devinfolist)
Append PortAudio backend devices to the list.
size_t us
We often use boolean values.