hydrogen 1.2.3
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-2024 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
65 : AudioOutput()
66 , m_processCallback( processCallback )
67 , m_pOut_L( nullptr )
68 , m_pOut_R( nullptr )
69 , m_pStream( nullptr )
70{
73}
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 int err;
166 if ( ! m_bInitialised ) {
167 err = Pa_Initialize();
168
169 if ( err != paNoError ) {
170 ERRORLOG( "Portaudio error in Pa_Initialize: " + QString( Pa_GetErrorText( err ) ) );
171 return 1;
172 }
173 m_bInitialised = true;
174
175 }
176
177
178 // Find device to use
179 int nDevices = Pa_GetDeviceCount();
180 const PaDeviceInfo *pDeviceInfo;
181 for ( int nDevice = 0; nDevice < nDevices; nDevice++ ) {
182 pDeviceInfo = Pa_GetDeviceInfo( nDevice );
183 if ( pDeviceInfo == nullptr ) {
184 continue;
185 }
186
187 // Filter by HostAPI
188 if ( ! pPreferences->m_sPortAudioHostAPI.isNull() || pPreferences->m_sPortAudioHostAPI != "" ) {
189 auto pInfo = Pa_GetHostApiInfo( pDeviceInfo->hostApi );
190 if ( pInfo == nullptr || pInfo->name != pPreferences->m_sPortAudioHostAPI ) {
191 continue;
192 }
193 }
194
195 if ( pDeviceInfo->maxOutputChannels >= 2
196 && ( QString::compare( m_sDevice, pDeviceInfo->name, Qt::CaseInsensitive ) == 0 ||
197 m_sDevice.isNull() || m_sDevice == "" ) ) {
198 PaStreamParameters outputParameters;
199 memset( &outputParameters, '\0', sizeof( outputParameters ) );
200 outputParameters.channelCount = 2;
201 outputParameters.device = nDevice;
202 outputParameters.hostApiSpecificStreamInfo = nullptr;
203 outputParameters.sampleFormat = paFloat32;
204
205 // Use the same latency setting as Pa_OpenDefaultStream() -- defaulting to the high suggested
206 // latency. This should probably be an option.
207 outputParameters.suggestedLatency =
208 Pa_GetDeviceInfo( nDevice )->defaultHighInputLatency;
209 if ( pPreferences->m_nLatencyTarget > 0 ) {
210 outputParameters.suggestedLatency = pPreferences->m_nLatencyTarget * 1.0 / getSampleRate();
211 }
212
213 err = Pa_OpenStream( &m_pStream,
214 nullptr, /* No input stream */
215 &outputParameters,
216 m_nSampleRate, paFramesPerBufferUnspecified, paNoFlag,
217 portAudioCallback, this );
218 if ( err != paNoError ) {
219 ERRORLOG( QString( "Found but can't open device '%1' (max %3 in, %4 out): %2" )
220 .arg( m_sDevice ).arg( Pa_GetErrorText( err ) )
221 .arg( pDeviceInfo->maxInputChannels ).arg( pDeviceInfo->maxOutputChannels ) );
222 // Use the default stream
223 break;
224 }
225 INFOLOG( QString( "Opened device '%1'" ).arg( m_sDevice ) );
226 bUseDefaultStream = false;
227 break;
228 }
229
230 if ( bUseDefaultStream ) {
231 ERRORLOG( QString( "Can't use device '%1', using default stream" )
232 .arg( m_sDevice ) );
233 }
234 }
235
236 if ( bUseDefaultStream ) {
237 // Failed to open the request device. Use the default device.
238 // Less than desirably, this will also use the default latency settings.
239 err = Pa_OpenDefaultStream(
240 &m_pStream, /* passes back stream pointer */
241 0, /* no input channels */
242 2, /* stereo output */
243 paFloat32, /* 32 bit floating point output */
244 m_nSampleRate, // sample rate
245 paFramesPerBufferUnspecified, // frames per buffer
246 portAudioCallback, /* specify our custom callback */
247 this ); /* pass our data through to callback */
248 }
249
250 if ( err != paNoError ) {
251 ERRORLOG( "Portaudio error in Pa_OpenDefaultStream: " + QString( Pa_GetErrorText( err ) ) );
252 return 1;
253 }
254
255 if ( m_pStream == nullptr ) {
256 ERRORLOG( "Invalid stream." );
257 return 1;
258 }
259
260 const PaStreamInfo *pStreamInfo = Pa_GetStreamInfo( m_pStream );
261 if ( pStreamInfo == nullptr ) {
262 ERRORLOG( "Invalid stream info." );
263 return 1;
264 }
265
266 if ( (unsigned) pStreamInfo->sampleRate != m_nSampleRate ) {
267 ERRORLOG( QString( "Couldn't get sample rate %d, using %d instead" ).arg( m_nSampleRate ).arg( pStreamInfo->sampleRate ) );
268 m_nSampleRate = (unsigned) pStreamInfo->sampleRate;
269 }
270 INFOLOG( QString( "PortAudio outpot latency: %1 s" ).arg( pStreamInfo->outputLatency ) );
271
272 err = Pa_StartStream( m_pStream );
273
274
275 if ( err != paNoError ) {
276 ERRORLOG( "Portaudio error in Pa_StartStream: " + QString( Pa_GetErrorText( err ) ) );
277 return 1;
278 }
279 return 0;
280}
281
283{
284 if ( m_pStream != nullptr ) {
285
286 int err = Pa_StopStream( m_pStream );
287 if ( err != paNoError ) {
288 ERRORLOG( "Err: " + QString( Pa_GetErrorText( err ) ) );
289 }
290
291 err = Pa_CloseStream( m_pStream );
292 if ( err != paNoError ) {
293 ERRORLOG( "Err: " + QString( Pa_GetErrorText( err ) ) );
294 }
295 }
296
297 m_bInitialised = false;
298 Pa_Terminate();
299
300 delete[] m_pOut_L;
301 m_pOut_L = nullptr;
302
303 delete[] m_pOut_R;
304 m_pOut_R = nullptr;
305}
306
308{
309 return MAX_BUFFER_SIZE;
310}
311
313{
314 return m_nSampleRate;
315}
316
318{
319 if ( m_pStream == nullptr ) {
320 return 0;
321 }
322
323 const PaStreamInfo *pStreamInfo = Pa_GetStreamInfo( m_pStream );
324 if ( pStreamInfo == nullptr ) {
325 ERRORLOG( "Invalid stream info" );
326 return 0;
327 }
328
329 return std::max( static_cast<int>( pStreamInfo->outputLatency * getSampleRate() ),
330 0 );
331}
332
334{
335 return m_pOut_L;
336}
337
339{
340 return m_pOut_R;
341}
342
343};
344
345#endif
346
#define INFOLOG(x)
Definition Object.h:237
#define WARNINGLOG(x)
Definition Object.h:238
#define ERRORLOG(x)
Definition Object.h:239
#define ___ERRORLOG(x)
Definition Object.h:257
Base abstract class for audio output classes.
Definition AudioOutput.h:39
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:78
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)