hydrogen 1.2.6
PortAudioDriver.cpp
Go to the documentation of this file.
1/*
2 * Hydrogen
3 * Copyright(c) 2002-2008 by Alex >Comix< Cominu [comix@users.sourceforge.net]
4 * Copyright(c) 2008-2025 The hydrogen development team [hydrogen-devel@lists.sourceforge.net]
5 *
6 * http://www.hydrogen-music.org
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY, without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see https://www.gnu.org/licenses
20 *
21 */
22
24#if defined(H2CORE_HAVE_PORTAUDIO) || _DOXYGEN_
25
26#include <iostream>
27
29namespace H2Core
30{
31
33 const void *inputBuffer,
34 void *outputBuffer,
35 unsigned long framesPerBuffer,
36 const PaStreamCallbackTimeInfo* timeInfo,
37 PaStreamCallbackFlags statusFlags,
38 void *userData )
39{
40 float *out = ( float* )outputBuffer;
41 PortAudioDriver *pDriver = ( PortAudioDriver* )userData;
42 if ( pDriver == nullptr ) {
43 ___ERRORLOG( "Invalid driver pointer" );
44 return 1;
45 }
46
47 while ( framesPerBuffer > 0 ) {
48 unsigned long nFrames = std::min( (unsigned long) MAX_BUFFER_SIZE, framesPerBuffer );
49 pDriver->m_processCallback( nFrames, nullptr );
50
51 for ( unsigned i = 0; i < nFrames; i++ ) {
52 *out++ = pDriver->m_pOut_L[ i ];
53 *out++ = pDriver->m_pOut_R[ i ];
54 }
55 framesPerBuffer -= nFrames;
56 }
57 return 0;
58}
59
60
62
63
74
75
78
79int PortAudioDriver::init( unsigned nBufferSize )
80{
81 return 0;
82}
83
84
85// String list of API names
87{
88 if ( ! m_bInitialised ) {
89 Pa_Initialize();
90 m_bInitialised = true;
91 }
92
93 QStringList hostAPIs;
94 int nHostAPIs = Pa_GetHostApiCount();
95 for ( int n = 0; n < nHostAPIs; n++ ) {
96 const PaHostApiInfo *pHostApiInfo = Pa_GetHostApiInfo( (PaHostApiIndex)n );
97 if ( pHostApiInfo == nullptr ) {
98 ERRORLOG( QString( "Invalid host API [%1]" ).arg( n ) );
99 continue;
100 }
101 hostAPIs.push_back( pHostApiInfo->name );
102 }
103
104 return hostAPIs;
105}
106
107// List devices
108QStringList PortAudioDriver::getDevices( QString HostAPI ) {
109 if ( ! m_bInitialised ) {
110 Pa_Initialize();
111 m_bInitialised = true;
112 }
113
114 QStringList devices;
115 if ( HostAPI.isNull() || HostAPI == "" ) {
116 WARNINGLOG( "Using default HostAPI" );
117 auto pInfo = Pa_GetHostApiInfo( Pa_GetDefaultHostApi() );
118 if ( pInfo == nullptr ) {
119 ERRORLOG( "Unable to obtain default Host API" );
120 return devices;
121 }
122
123 HostAPI = pInfo->name;
124 }
125
126 int nDevices = Pa_GetDeviceCount();
127 for ( int nDevice = 0; nDevice < nDevices; nDevice++ ) {
128 const PaDeviceInfo *pDeviceInfo = Pa_GetDeviceInfo( nDevice );
129 if ( pDeviceInfo == nullptr ) {
130 continue;
131 }
132
133 // Filter by API
134 auto pInfo = Pa_GetHostApiInfo( pDeviceInfo->hostApi );
135 if ( pInfo == nullptr || pInfo->name != HostAPI ) {
136 continue;
137 }
138 if ( pDeviceInfo->maxOutputChannels >= 2 ) {
139 devices.push_back( QString( pDeviceInfo->name ) );
140 }
141 }
142
143 return devices;
144}
145
147 Preferences *pPreferences = Preferences::get_instance();
148 return getDevices( pPreferences->m_sPortAudioHostAPI );
149}
150
151//
152// Connect
153// return 0: Ok
154// return 1: Generic error
155//
157{
158 bool bUseDefaultStream = true;
159 Preferences *pPreferences = Preferences::get_instance();
160 INFOLOG( "[connect]" );
161
162 m_pOut_L = new float[ MAX_BUFFER_SIZE ];
163 m_pOut_R = new float[ MAX_BUFFER_SIZE ];
164
165 // Reset buffers to avoid noise during startup (e.g. in case the callback
166 // was not able to obtain the audio engine lock, the arbitrary numbers
167 // filling the buffer after creation will be passed to the audio output).
168 for ( int ii = 0; ii < MAX_BUFFER_SIZE; ii++ ) {
169 m_pOut_L[ ii ] = 0;
170 m_pOut_R[ ii ] = 0;
171 }
172
173 int err;
174 if ( ! m_bInitialised ) {
175 err = Pa_Initialize();
176
177 if ( err != paNoError ) {
178 ERRORLOG( "Portaudio error in Pa_Initialize: " + QString( Pa_GetErrorText( err ) ) );
179 return 1;
180 }
181 m_bInitialised = true;
182
183 }
184
185
186 // Find device to use
187 int nDevices = Pa_GetDeviceCount();
188 const PaDeviceInfo *pDeviceInfo;
189 for ( int nDevice = 0; nDevice < nDevices; nDevice++ ) {
190 pDeviceInfo = Pa_GetDeviceInfo( nDevice );
191 if ( pDeviceInfo == nullptr ) {
192 continue;
193 }
194
195 // Filter by HostAPI
196 if ( ! pPreferences->m_sPortAudioHostAPI.isNull() || pPreferences->m_sPortAudioHostAPI != "" ) {
197 auto pInfo = Pa_GetHostApiInfo( pDeviceInfo->hostApi );
198 if ( pInfo == nullptr || pInfo->name != pPreferences->m_sPortAudioHostAPI ) {
199 continue;
200 }
201 }
202
203 if ( pDeviceInfo->maxOutputChannels >= 2
204 && ( QString::compare( m_sDevice, pDeviceInfo->name, Qt::CaseInsensitive ) == 0 ||
205 m_sDevice.isNull() || m_sDevice == "" ) ) {
206 PaStreamParameters outputParameters;
207 memset( &outputParameters, '\0', sizeof( outputParameters ) );
208 outputParameters.channelCount = 2;
209 outputParameters.device = nDevice;
210 outputParameters.hostApiSpecificStreamInfo = nullptr;
211 outputParameters.sampleFormat = paFloat32;
212
213 // Use the same latency setting as Pa_OpenDefaultStream() -- defaulting to the high suggested
214 // latency. This should probably be an option.
215 outputParameters.suggestedLatency =
216 Pa_GetDeviceInfo( nDevice )->defaultHighInputLatency;
217 if ( pPreferences->m_nLatencyTarget > 0 ) {
218 outputParameters.suggestedLatency = pPreferences->m_nLatencyTarget * 1.0 / getSampleRate();
219 }
220
221 err = Pa_OpenStream( &m_pStream,
222 nullptr, /* No input stream */
223 &outputParameters,
224 m_nSampleRate, paFramesPerBufferUnspecified, paNoFlag,
225 portAudioCallback, this );
226 if ( err != paNoError ) {
227 ERRORLOG( QString( "Found but can't open device '%1' (max %3 in, %4 out): %2" )
228 .arg( m_sDevice ).arg( Pa_GetErrorText( err ) )
229 .arg( pDeviceInfo->maxInputChannels ).arg( pDeviceInfo->maxOutputChannels ) );
230 // Use the default stream
231 break;
232 }
233 INFOLOG( QString( "Opened device '%1'" ).arg( m_sDevice ) );
234 bUseDefaultStream = false;
235 break;
236 }
237
238 if ( bUseDefaultStream ) {
239 ERRORLOG( QString( "Can't use device '%1', using default stream" )
240 .arg( m_sDevice ) );
241 }
242 }
243
244 if ( bUseDefaultStream ) {
245 // Failed to open the request device. Use the default device.
246 // Less than desirably, this will also use the default latency settings.
247 err = Pa_OpenDefaultStream(
248 &m_pStream, /* passes back stream pointer */
249 0, /* no input channels */
250 2, /* stereo output */
251 paFloat32, /* 32 bit floating point output */
252 m_nSampleRate, // sample rate
253 paFramesPerBufferUnspecified, // frames per buffer
254 portAudioCallback, /* specify our custom callback */
255 this ); /* pass our data through to callback */
256 }
257
258 if ( err != paNoError ) {
259 ERRORLOG( "Portaudio error in Pa_OpenDefaultStream: " + QString( Pa_GetErrorText( err ) ) );
260 return 1;
261 }
262
263 if ( m_pStream == nullptr ) {
264 ERRORLOG( "Invalid stream." );
265 return 1;
266 }
267
268 const PaStreamInfo *pStreamInfo = Pa_GetStreamInfo( m_pStream );
269 if ( pStreamInfo == nullptr ) {
270 ERRORLOG( "Invalid stream info." );
271 return 1;
272 }
273
274 if ( (unsigned) pStreamInfo->sampleRate != m_nSampleRate ) {
275 ERRORLOG( QString( "Couldn't get sample rate %d, using %d instead" ).arg( m_nSampleRate ).arg( pStreamInfo->sampleRate ) );
276 m_nSampleRate = (unsigned) pStreamInfo->sampleRate;
277 }
278 INFOLOG( QString( "PortAudio outpot latency: %1 s" ).arg( pStreamInfo->outputLatency ) );
279
280 err = Pa_StartStream( m_pStream );
281
282
283 if ( err != paNoError ) {
284 ERRORLOG( "Portaudio error in Pa_StartStream: " + QString( Pa_GetErrorText( err ) ) );
285 return 1;
286 }
287 return 0;
288}
289
291{
292 if ( m_pStream != nullptr ) {
293
294 int err = Pa_StopStream( m_pStream );
295 if ( err != paNoError ) {
296 ERRORLOG( "Err: " + QString( Pa_GetErrorText( err ) ) );
297 }
298
299 err = Pa_CloseStream( m_pStream );
300 if ( err != paNoError ) {
301 ERRORLOG( "Err: " + QString( Pa_GetErrorText( err ) ) );
302 }
303 }
304
305 m_bInitialised = false;
306 Pa_Terminate();
307
308 delete[] m_pOut_L;
309 m_pOut_L = nullptr;
310
311 delete[] m_pOut_R;
312 m_pOut_R = nullptr;
313}
314
316{
317 return MAX_BUFFER_SIZE;
318}
319
321{
322 return m_nSampleRate;
323}
324
326{
327 if ( m_pStream == nullptr ) {
328 return 0;
329 }
330
331 const PaStreamInfo *pStreamInfo = Pa_GetStreamInfo( m_pStream );
332 if ( pStreamInfo == nullptr ) {
333 ERRORLOG( "Invalid stream info" );
334 return 0;
335 }
336
337 return std::max( static_cast<int>( pStreamInfo->outputLatency * getSampleRate() ),
338 0 );
339}
340
342{
343 return m_pOut_L;
344}
345
347{
348 return m_pOut_R;
349}
350
351};
352
353#endif
354
#define INFOLOG(x)
Definition Object.h:240
#define WARNINGLOG(x)
Definition Object.h:241
#define ERRORLOG(x)
Definition Object.h:242
#define ___ERRORLOG(x)
Definition Object.h:260
virtual void disconnect() override
virtual float * getOut_L() override
virtual int init(unsigned nBufferSize) override
virtual int getLatency() override
Approximate audio latency (in frames) A reasonable approximation is the buffer time on most audio sys...
virtual float * getOut_R() override
virtual unsigned getBufferSize() override
PortAudioDriver(audioProcessCallback processCallback)
virtual int connect() override
static QStringList getDevices()
audioProcessCallback m_processCallback
virtual unsigned getSampleRate() override
static QStringList getHostAPIs()
Manager for User Preferences File (singleton)
Definition Preferences.h:79
static Preferences * get_instance()
Returns a pointer to the current Preferences singleton stored in __instance.
unsigned m_nSampleRate
Sample rate of the audio.
QString m_sPortAudioHostAPI
#define MAX_BUFFER_SIZE
Maximum buffer size.
Definition config.dox:87
int(* audioProcessCallback)(uint32_t, void *)
Definition AudioOutput.h:32
int portAudioCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData)