1 /*
2 * AudioPortAudio.cpp - device-class that performs PCM-output via PortAudio
3 *
4 * Copyright (c) 2008 Csaba Hruska <csaba.hruska/at/gmail.com>
5 * Copyright (c) 2010 Tobias Doerffel <tobydox/at/users.sourceforge.net>
6 *
7 * This file is part of LMMS - https://lmms.io
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public
11 * License as published by the Free Software Foundation; either
12 * version 2 of the License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public
20 * License along with this program (see COPYING); if not, write to the
21 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 * Boston, MA 02110-1301 USA.
23 *
24 */
25
26
27 #include "AudioPortAudio.h"
28
29 #ifndef LMMS_HAVE_PORTAUDIO
updateBackends()30 void AudioPortAudioSetupUtil::updateBackends()
31 {
32 }
33
updateDevices()34 void AudioPortAudioSetupUtil::updateDevices()
35 {
36 }
37
updateChannels()38 void AudioPortAudioSetupUtil::updateChannels()
39 {
40 }
41 #endif
42
43 #ifdef LMMS_HAVE_PORTAUDIO
44
45 #include <QLabel>
46 #include <QLineEdit>
47
48 #include "Engine.h"
49 #include "ConfigManager.h"
50 #include "gui_templates.h"
51 #include "templates.h"
52 #include "ComboBox.h"
53 #include "Mixer.h"
54
55
AudioPortAudio(bool & _success_ful,Mixer * _mixer)56 AudioPortAudio::AudioPortAudio( bool & _success_ful, Mixer * _mixer ) :
57 AudioDevice( tLimit<ch_cnt_t>(
58 ConfigManager::inst()->value( "audioportaudio", "channels" ).toInt(),
59 DEFAULT_CHANNELS, SURROUND_CHANNELS ),
60 _mixer ),
61 m_paStream( NULL ),
62 m_wasPAInitError( false ),
63 m_outBuf( new surroundSampleFrame[mixer()->framesPerPeriod()] ),
64 m_outBufPos( 0 )
65 {
66 _success_ful = false;
67
68 m_outBufSize = mixer()->framesPerPeriod();
69
70 PaError err = Pa_Initialize();
71
72 if( err != paNoError ) {
73 printf( "Couldn't initialize PortAudio: %s\n", Pa_GetErrorText( err ) );
74 m_wasPAInitError = true;
75 return;
76 }
77
78 if( Pa_GetDeviceCount() <= 0 )
79 {
80 return;
81 }
82
83 const QString& backend = ConfigManager::inst()->value( "audioportaudio", "backend" );
84 const QString& device = ConfigManager::inst()->value( "audioportaudio", "device" );
85
86 PaDeviceIndex inDevIdx = -1;
87 PaDeviceIndex outDevIdx = -1;
88 const PaDeviceInfo * di;
89 for( int i = 0; i < Pa_GetDeviceCount(); ++i )
90 {
91 di = Pa_GetDeviceInfo( i );
92 if( di->name == device &&
93 Pa_GetHostApiInfo( di->hostApi )->name == backend )
94 {
95 inDevIdx = i;
96 outDevIdx = i;
97 }
98 }
99
100 if( inDevIdx < 0 )
101 {
102 inDevIdx = Pa_GetDefaultInputDevice();
103 }
104
105 if( outDevIdx < 0 )
106 {
107 outDevIdx = Pa_GetDefaultOutputDevice();
108 }
109
110 if( inDevIdx < 0 || outDevIdx < 0 )
111 {
112 return;
113 }
114
115 double inLatency = 0;//(double)mixer()->framesPerPeriod() / (double)sampleRate();
116 double outLatency = 0;//(double)mixer()->framesPerPeriod() / (double)sampleRate();
117
118 //inLatency = Pa_GetDeviceInfo( inDevIdx )->defaultLowInputLatency;
119 //outLatency = Pa_GetDeviceInfo( outDevIdx )->defaultLowOutputLatency;
120 const int samples = mixer()->framesPerPeriod();
121
122 // Configure output parameters.
123 m_outputParameters.device = outDevIdx;
124 m_outputParameters.channelCount = channels();
125 m_outputParameters.sampleFormat = paFloat32; // 32 bit floating point output
126 m_outputParameters.suggestedLatency = outLatency;
127 m_outputParameters.hostApiSpecificStreamInfo = NULL;
128
129 // Configure input parameters.
130 m_inputParameters.device = inDevIdx;
131 m_inputParameters.channelCount = DEFAULT_CHANNELS;
132 m_inputParameters.sampleFormat = paFloat32; // 32 bit floating point input
133 m_inputParameters.suggestedLatency = inLatency;
134 m_inputParameters.hostApiSpecificStreamInfo = NULL;
135
136 // Open an audio I/O stream.
137 err = Pa_OpenStream(
138 &m_paStream,
139 supportsCapture() ? &m_inputParameters : NULL, // The input parameter
140 &m_outputParameters, // The outputparameter
141 sampleRate(),
142 samples,
143 paNoFlag, // Don't use any flags
144 _process_callback, // our callback function
145 this );
146
147 if( err == paInvalidDevice && sampleRate() < 48000 )
148 {
149 printf("Pa_OpenStream() failed with 44,1 KHz, trying again with 48 KHz\n");
150 // some backends or drivers do not allow 32 bit floating point data
151 // with a samplerate of 44100 Hz
152 setSampleRate( 48000 );
153 err = Pa_OpenStream(
154 &m_paStream,
155 supportsCapture() ? &m_inputParameters : NULL, // The input parameter
156 &m_outputParameters, // The outputparameter
157 sampleRate(),
158 samples,
159 paNoFlag, // Don't use any flags
160 _process_callback, // our callback function
161 this );
162 }
163
164 if( err != paNoError )
165 {
166 printf( "Couldn't open PortAudio: %s\n", Pa_GetErrorText( err ) );
167 return;
168 }
169
170 printf( "Input device: '%s' backend: '%s'\n", Pa_GetDeviceInfo( inDevIdx )->name, Pa_GetHostApiInfo( Pa_GetDeviceInfo( inDevIdx )->hostApi )->name );
171 printf( "Output device: '%s' backend: '%s'\n", Pa_GetDeviceInfo( outDevIdx )->name, Pa_GetHostApiInfo( Pa_GetDeviceInfo( outDevIdx )->hostApi )->name );
172
173 // TODO: debug Mixer::pushInputFrames()
174 //m_supportsCapture = true;
175
176 _success_ful = true;
177 }
178
179
180
181
~AudioPortAudio()182 AudioPortAudio::~AudioPortAudio()
183 {
184 stopProcessing();
185
186 if( !m_wasPAInitError )
187 {
188 Pa_Terminate();
189 }
190 delete[] m_outBuf;
191 }
192
193
194
195
startProcessing()196 void AudioPortAudio::startProcessing()
197 {
198 m_stopped = false;
199 PaError err = Pa_StartStream( m_paStream );
200
201 if( err != paNoError )
202 {
203 m_stopped = true;
204 printf( "PortAudio error: %s\n", Pa_GetErrorText( err ) );
205 }
206 }
207
208
209
210
stopProcessing()211 void AudioPortAudio::stopProcessing()
212 {
213 if( m_paStream && Pa_IsStreamActive( m_paStream ) )
214 {
215 m_stopped = true;
216 PaError err = Pa_StopStream( m_paStream );
217
218 if( err != paNoError )
219 {
220 printf( "PortAudio error: %s\n", Pa_GetErrorText( err ) );
221 }
222 }
223 }
224
225
226
227
applyQualitySettings()228 void AudioPortAudio::applyQualitySettings()
229 {
230 if( hqAudio() )
231 {
232
233 setSampleRate( Engine::mixer()->processingSampleRate() );
234 int samples = mixer()->framesPerPeriod();
235
236 PaError err = Pa_OpenStream(
237 &m_paStream,
238 supportsCapture() ? &m_inputParameters : NULL, // The input parameter
239 &m_outputParameters, // The outputparameter
240 sampleRate(),
241 samples,
242 paNoFlag, // Don't use any flags
243 _process_callback, // our callback function
244 this );
245
246 if( err != paNoError )
247 {
248 printf( "Couldn't open PortAudio: %s\n", Pa_GetErrorText( err ) );
249 return;
250 }
251 }
252
253 AudioDevice::applyQualitySettings();
254 }
255
256
257
process_callback(const float * _inputBuffer,float * _outputBuffer,unsigned long _framesPerBuffer)258 int AudioPortAudio::process_callback(
259 const float *_inputBuffer,
260 float * _outputBuffer,
261 unsigned long _framesPerBuffer )
262 {
263 if( supportsCapture() )
264 {
265 mixer()->pushInputFrames( (sampleFrame*)_inputBuffer,
266 _framesPerBuffer );
267 }
268
269 if( m_stopped )
270 {
271 memset( _outputBuffer, 0, _framesPerBuffer *
272 channels() * sizeof(float) );
273 return paComplete;
274 }
275
276 while( _framesPerBuffer )
277 {
278 if( m_outBufPos == 0 )
279 {
280 // frames depend on the sample rate
281 const fpp_t frames = getNextBuffer( m_outBuf );
282 if( !frames )
283 {
284 m_stopped = true;
285 memset( _outputBuffer, 0, _framesPerBuffer *
286 channels() * sizeof(float) );
287 return paComplete;
288 }
289 m_outBufSize = frames;
290 }
291 const int min_len = qMin( (int)_framesPerBuffer,
292 m_outBufSize - m_outBufPos );
293
294 float master_gain = mixer()->masterGain();
295
296 for( fpp_t frame = 0; frame < min_len; ++frame )
297 {
298 for( ch_cnt_t chnl = 0; chnl < channels(); ++chnl )
299 {
300 ( _outputBuffer + frame * channels() )[chnl] =
301 Mixer::clip( m_outBuf[frame][chnl] *
302 master_gain );
303 }
304 }
305
306 _outputBuffer += min_len * channels();
307 _framesPerBuffer -= min_len;
308 m_outBufPos += min_len;
309 m_outBufPos %= m_outBufSize;
310 }
311
312 return paContinue;
313 }
314
315
316
_process_callback(const void * _inputBuffer,void * _outputBuffer,unsigned long _framesPerBuffer,const PaStreamCallbackTimeInfo * _timeInfo,PaStreamCallbackFlags _statusFlags,void * _arg)317 int AudioPortAudio::_process_callback(
318 const void *_inputBuffer,
319 void * _outputBuffer,
320 unsigned long _framesPerBuffer,
321 const PaStreamCallbackTimeInfo * _timeInfo,
322 PaStreamCallbackFlags _statusFlags,
323 void * _arg )
324 {
325 Q_UNUSED(_timeInfo);
326 Q_UNUSED(_statusFlags);
327
328 AudioPortAudio * _this = static_cast<AudioPortAudio *> (_arg);
329 return _this->process_callback( (const float*)_inputBuffer,
330 (float*)_outputBuffer, _framesPerBuffer );
331 }
332
333
334
335
updateBackends()336 void AudioPortAudioSetupUtil::updateBackends()
337 {
338 PaError err = Pa_Initialize();
339 if( err != paNoError ) {
340 printf( "Couldn't initialize PortAudio: %s\n", Pa_GetErrorText( err ) );
341 return;
342 }
343
344 const PaHostApiInfo * hi;
345 for( int i = 0; i < Pa_GetHostApiCount(); ++i )
346 {
347 hi = Pa_GetHostApiInfo( i );
348 m_backendModel.addItem( hi->name );
349 }
350
351 Pa_Terminate();
352 }
353
354
355
356
updateDevices()357 void AudioPortAudioSetupUtil::updateDevices()
358 {
359 PaError err = Pa_Initialize();
360 if( err != paNoError ) {
361 printf( "Couldn't initialize PortAudio: %s\n", Pa_GetErrorText( err ) );
362 return;
363 }
364
365 // get active backend
366 const QString& backend = m_backendModel.currentText();
367 int hostApi = 0;
368 const PaHostApiInfo * hi;
369 for( int i = 0; i < Pa_GetHostApiCount(); ++i )
370 {
371 hi = Pa_GetHostApiInfo( i );
372 if( backend == hi->name )
373 {
374 hostApi = i;
375 break;
376 }
377 }
378
379 // get devices for selected backend
380 m_deviceModel.clear();
381 const PaDeviceInfo * di;
382 for( int i = 0; i < Pa_GetDeviceCount(); ++i )
383 {
384 di = Pa_GetDeviceInfo( i );
385 if( di->hostApi == hostApi )
386 {
387 m_deviceModel.addItem( di->name );
388 }
389 }
390 Pa_Terminate();
391 }
392
393
394
395
updateChannels()396 void AudioPortAudioSetupUtil::updateChannels()
397 {
398 PaError err = Pa_Initialize();
399 if( err != paNoError ) {
400 printf( "Couldn't initialize PortAudio: %s\n", Pa_GetErrorText( err ) );
401 return;
402 }
403 // get active backend
404 Pa_Terminate();
405 }
406
407
408
409
setupWidget(QWidget * _parent)410 AudioPortAudio::setupWidget::setupWidget( QWidget * _parent ) :
411 AudioDeviceSetupWidget( AudioPortAudio::name(), _parent )
412 {
413 m_backend = new ComboBox( this, "BACKEND" );
414 m_backend->setGeometry( 64, 15, 260, 20 );
415
416 QLabel * backend_lbl = new QLabel( tr( "BACKEND" ), this );
417 backend_lbl->setFont( pointSize<7>( backend_lbl->font() ) );
418 backend_lbl->move( 8, 18 );
419
420 m_device = new ComboBox( this, "DEVICE" );
421 m_device->setGeometry( 64, 35, 260, 20 );
422
423 QLabel * dev_lbl = new QLabel( tr( "DEVICE" ), this );
424 dev_lbl->setFont( pointSize<7>( dev_lbl->font() ) );
425 dev_lbl->move( 8, 38 );
426
427 /* LcdSpinBoxModel * m = new LcdSpinBoxModel( );
428 m->setRange( DEFAULT_CHANNELS, SURROUND_CHANNELS );
429 m->setStep( 2 );
430 m->setValue( ConfigManager::inst()->value( "audioportaudio",
431 "channels" ).toInt() );
432
433 m_channels = new LcdSpinBox( 1, this );
434 m_channels->setModel( m );
435 m_channels->setLabel( tr( "CHANNELS" ) );
436 m_channels->move( 308, 20 );*/
437
438 connect( &m_setupUtil.m_backendModel, SIGNAL( dataChanged() ),
439 &m_setupUtil, SLOT( updateDevices() ) );
440
441 connect( &m_setupUtil.m_deviceModel, SIGNAL( dataChanged() ),
442 &m_setupUtil, SLOT( updateChannels() ) );
443
444 m_backend->setModel( &m_setupUtil.m_backendModel );
445 m_device->setModel( &m_setupUtil.m_deviceModel );
446 }
447
448
449
450
~setupWidget()451 AudioPortAudio::setupWidget::~setupWidget()
452 {
453 disconnect( &m_setupUtil.m_backendModel, SIGNAL( dataChanged() ),
454 &m_setupUtil, SLOT( updateDevices() ) );
455
456 disconnect( &m_setupUtil.m_deviceModel, SIGNAL( dataChanged() ),
457 &m_setupUtil, SLOT( updateChannels() ) );
458 }
459
460
461
462
saveSettings()463 void AudioPortAudio::setupWidget::saveSettings()
464 {
465
466 ConfigManager::inst()->setValue( "audioportaudio", "backend",
467 m_setupUtil.m_backendModel.currentText() );
468 ConfigManager::inst()->setValue( "audioportaudio", "device",
469 m_setupUtil.m_deviceModel.currentText() );
470 /* ConfigManager::inst()->setValue( "audioportaudio", "channels",
471 QString::number( m_channels->value<int>() ) );*/
472
473 }
474
475
476
477
show()478 void AudioPortAudio::setupWidget::show()
479 {
480 if( m_setupUtil.m_backendModel.size() == 0 )
481 {
482 // populate the backend model the first time we are shown
483 m_setupUtil.updateBackends();
484
485 const QString& backend = ConfigManager::inst()->value(
486 "audioportaudio", "backend" );
487 const QString& device = ConfigManager::inst()->value(
488 "audioportaudio", "device" );
489
490 int i = qMax( 0, m_setupUtil.m_backendModel.findText( backend ) );
491 m_setupUtil.m_backendModel.setValue( i );
492
493 m_setupUtil.updateDevices();
494
495 i = qMax( 0, m_setupUtil.m_deviceModel.findText( device ) );
496 m_setupUtil.m_deviceModel.setValue( i );
497 }
498
499 AudioDeviceSetupWidget::show();
500 }
501
502
503 #endif
504
505
506
507