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