VAE - Virtual Audio Engine 1
Small Data Driven Audio Engine
vae_portaudio.hpp
Go to the documentation of this file.
1#ifndef _VAE_PORTAUDIO
2#define _VAE_PORTAUDIO
3
4#include "./vae_device.hpp"
5#include "../vae_config.hpp"
6#include "../vae_util.hpp"
7
8#include "../../../external/portaudio/include/portaudio.h"
9#include "../../../external/portaudio/src/common/pa_debugprint.h"
10
11namespace vae { namespace core {
12 /**
13 * @brief Portaudio backend implementation.
14 */
15 class DevicePortaudio final : public Device {
16 PaStream *mStream = nullptr;
17 bool mInitialized = false;
18
19 void cleanUp() {
20 VAE_PROFILER_SCOPE_NAMED("Cleanup portaudio")
21 if (!mInitialized || mStream == nullptr) {
22 return;
23 }
24
25 {
26 VAE_PROFILER_SCOPE_NAMED("Stop portaudio stream")
27 PaError err = Pa_StopStream(mStream);
28 VAE_ASSERT(err == paNoError)
29 }
30 {
31 VAE_PROFILER_SCOPE_NAMED("Close portaudio stream")
32 PaError err = Pa_CloseStream(mStream);
33 VAE_ASSERT(err == paNoError)
34 }
35 mInitialized = false;
36 }
37
38 /**
39 * @brief Function called from the PortAudio thread.
40 */
41 static int AudioCallback(
42 const void *in, void *out, unsigned long frames,
43 const PaStreamCallbackTimeInfo* timeInfo,
44 PaStreamCallbackFlags statusFlags, void *data
45 ) {
46 (void) timeInfo; // Prevent unused variable warnings.
47 (void) statusFlags; // Prevent unused variable warnings.
48 auto inFloat = static_cast<const float*>(in);
49 auto outFloat = static_cast<float*>(out);
50 static_cast<AudioThreadWorker*>(data)
51 ->swapBufferInterleaved(inFloat, outFloat, frames);
52 return paContinue;
53 }
54
55 static void StreamFinished(void* data) {
56 (void) data; // Prevent unused variable warnings.
57 }
58
59 public:
61
63 Backend& backend, const EngineConfig& config
64 ) : Device(backend, config) { }
65
67
68 bool openDevice(DeviceInfo& device) override {
69 const PaDeviceInfo* deviceInfo = Pa_GetDeviceInfo(device.id);
70
71 if (deviceInfo == nullptr) {
72 VAE_ASSERT(false)
73 return false;
74 }
75
76 device.channelsIn = tklb::clamp<int>(device.channelsIn, 0, StaticConfig::MaxChannels);
77 device.channelsOut = tklb::clamp<int>(device.channelsOut, 0, StaticConfig::MaxChannels);
78
79 PaStreamParameters inputParameters;
80 inputParameters.device = device.id;
81 inputParameters.sampleFormat = paFloat32;
82 inputParameters.suggestedLatency = deviceInfo->defaultLowInputLatency;
83 inputParameters.hostApiSpecificStreamInfo = NULL;
84 VAE_ASSERT(device.channelsIn <= Uint(deviceInfo->maxInputChannels))
85 inputParameters.channelCount = device.channelsIn;
86
87 PaStreamParameters outputParameters;
88 outputParameters.device = device.id;
89 outputParameters.sampleFormat = paFloat32;
90 outputParameters.suggestedLatency = deviceInfo->defaultLowOutputLatency;
91 outputParameters.hostApiSpecificStreamInfo = NULL;
92 VAE_ASSERT(device.channelsOut <= Uint(deviceInfo->maxOutputChannels))
93 outputParameters.channelCount = device.channelsOut;
94
95 if (device.bufferSize == 0) {
97 }
98
99 PaError err;
100 {
101 VAE_PROFILER_SCOPE_NAMED("Open stream portaudio")
102 err = Pa_OpenStream(
103 &mStream,
104 0 < inputParameters.channelCount ? &inputParameters : NULL,
105 0 < outputParameters.channelCount ? &outputParameters : NULL,
106 mConfig.internalSampleRate, // try getting internal samplerate
107 device.bufferSize,
108 paClipOff, // no clipping, device will do that
110 &mWorker
111 );
112
113 }
114
115 if (err != paNoError) {
116 VAE_ASSERT(false)
117 cleanUp();
118 return false;
119 }
120
121 err = Pa_SetStreamFinishedCallback(
123 );
124
125 if (err != paNoError) {
126 VAE_ASSERT(false)
127 cleanUp();
128 return false;
129 }
130
131 // Might have gotten different samplerate
132 const PaStreamInfo* streamInfo = Pa_GetStreamInfo(mStream);
133 init(
134 Uint(streamInfo->sampleRate),
135 device.channelsIn, device.channelsOut,
136 device.bufferSize // Pa doesn't provide any info, so we assume we got what we wanted
137 );
138
139 {
140 VAE_PROFILER_SCOPE_NAMED("Start stream portaudio")
141 err = Pa_StartStream(mStream);
142 }
143
144 if (err != paNoError) {
145 VAE_ASSERT(false)
146 cleanUp();
147 return false;
148 }
149 postInit();
150 return true;
151 }
152
153 bool closeDevice() override {
154 cleanUp();
155 return true;
156 }
157 };
158
159 class BackendPortAudio final : public Backend {
160 static void debugLog(const char* message) {
161 VAE_DEBUG("PortAudio: %s", message)
162 }
163
165 /**
166 * Should set up logging, but most of PortAudios underlying
167 * APIs don't care about this and print to stdout
168 */
169 PaUtil_SetDebugPrintFunction(&debugLog);
170 PaError err = Pa_Initialize();
171 if (err != paNoError) {
172 VAE_ASSERT(false)
173 return;
174 }
175 }
176
178 Pa_Terminate();
179 }
180 public:
182 static BackendPortAudio backend;
183 return backend;
184 }
185
186 unsigned int getDeviceCount() override {
187 return Pa_GetDeviceCount();
188 }
189
190 DeviceInfo getDevice(unsigned int index) override {
191 const PaDeviceInfo* deviceInfo = Pa_GetDeviceInfo(index);
192 if (deviceInfo == nullptr) {
193 VAE_ASSERT(false)
194 return DeviceInfo();
195 }
196 DeviceInfo info;
197 info.id = index;
198 info.channelsIn = deviceInfo->maxInputChannels;
199 info.channelsOut = deviceInfo->maxOutputChannels;
200 info.sampleRate = Uint(deviceInfo->defaultSampleRate);
201 tklb::memory::stringCopy(info.name, deviceInfo->name, sizeof(DeviceInfo::name));
202 tklb::memory::stringCopy(info.api, getName(), sizeof(DeviceInfo::api), false);
203 return info;
204 };
205
206 const char* getName() const override { return "portaudio"; };
207
209 return getDevice(Pa_GetDefaultOutputDevice());
210 };
211
213 return getDevice(Pa_GetDefaultInputDevice());
214 };
215
216 Device* createDevice(const EngineConfig& config) override {
217 return new DevicePortaudio(*this, config);
218 }
219 };
220
221} } // namespace vae::core
222
223#endif // _VAE_PORTAUDIO
Backend interface used to query devices before creating a actual device object.
Definition: vae_backend.hpp:14
static void debugLog(const char *message)
const char * getName() const override
Returns name of the api.
unsigned int getDeviceCount() override
Gets number of devices, needed to iterate them.
DeviceInfo getDevice(unsigned int index) override
Returns a spefic device info for index.
DeviceInfo getDefaultOutputDevice() override
Device * createDevice(const EngineConfig &config) override
Creates a device instance for this backend.
DeviceInfo getDefaultInputDevice() override
static BackendPortAudio & instance()
Interface for audio devices.
Definition: vae_device.hpp:18
AudioThreadWorker mWorker
Definition: vae_device.hpp:102
const EngineConfig & mConfig
Definition: vae_device.hpp:24
void init(Size sampleRate, Uchar channelsIn, Uchar channelsOut, Size bufferSize)
initializes buffers, queues and resamplers if needed Has to be called in openDevice once the samplera...
Definition: vae_device.hpp:114
Portaudio backend implementation.
static void StreamFinished(void *data)
bool openDevice(DeviceInfo &device) override
Opens a specific audio device.
bool closeDevice() override
Closes the currently open device.
static int AudioCallback(const void *in, void *out, unsigned long frames, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *data)
Function called from the PortAudio thread.
static void stringCopy(char *dst, const char *src, size_t size, bool terminate=true)
Definition: TMemoryUtil.hpp:26
constexpr unsigned char MaxChannels
Maximum channel count used to pre allocate buffers.
Definition: vae.hpp:268
unsigned int Uint
Definition: vae_types.hpp:46
Contains Typedefinitions and basic structures use by the public API and internally.
Definition: vae.hpp:31
Basic struct describing a audio device.
Definition: vae.hpp:102
unsigned int sampleRate
TODO not used?
Definition: vae.hpp:104
unsigned char channelsIn
Definition: vae.hpp:108
unsigned int bufferSize
desired bufferSize
Definition: vae.hpp:107
unsigned char channelsOut
Definition: vae.hpp:109
char name[255]
Device name reported from backend.
Definition: vae.hpp:105
int id
Negative values for invalid device.
Definition: vae.hpp:103
char api[4]
API abbreviation.
Definition: vae.hpp:106
Settings for the engine defined at EnginePimpl::init.
Definition: vae.hpp:157
Size preferredBufferSize
Buffer size that will be requested from device.
Definition: vae.hpp:233
Size internalSampleRate
Samplerate requested from device.
Definition: vae.hpp:168
Data shared with audio thread.
Definition: vae_device.hpp:37
#define VAE_DEBUG(msg,...)
Definition: vae_logger.hpp:83
#define VAE_PROFILER_SCOPE_NAMED(name)
Profiles a scope and names it.
#define VAE_PROFILER_OVERLOAD_NEW()
Overloads new and delete of class to be tracked.
#define VAE_ASSERT(condition)
Definition: vae_util.hpp:11