Refactor PortAudio backend (#7444)

Refactors the PortAudio backend to fix issues with DirectSound and MME crackling and not loading properly, as well as to improve code quality and maintainability.

---------

Co-authored-by: Dalton Messmer <messmer.dalton@gmail.com>
This commit is contained in:
Sotonye Atemie
2026-02-01 14:23:44 -05:00
committed by GitHub
parent f504204ef1
commit c14a327d31
6 changed files with 321 additions and 486 deletions

View File

@@ -113,6 +113,11 @@ protected:
m_sampleRate = _new_sr;
}
void setChannels(const ch_cnt_t channels)
{
m_channels = channels;
}
AudioEngine* audioEngine()
{
return m_audioEngine;

View File

@@ -25,136 +25,91 @@
#ifndef LMMS_AUDIO_PORTAUDIO_H
#define LMMS_AUDIO_PORTAUDIO_H
#include <QObject>
#include "lmmsconfig.h"
#include "ComboBoxModel.h"
#ifdef LMMS_HAVE_PORTAUDIO
# include <portaudio.h>
#include <QComboBox>
#include <QFormLayout>
#include <QString>
#include <QWidget>
#include <portaudio.h>
# include "AudioDevice.h"
# include "AudioDeviceSetupWidget.h"
#include "AudioDevice.h"
#include "AudioDeviceSetupWidget.h"
# if defined paNeverDropInput || defined paNonInterleaved
# define PORTAUDIO_V19
# else
# define PORTAUDIO_V18
# endif
namespace lmms {
#endif
namespace lmms
namespace detail {
class PortAudioInitializationGuard
{
class AudioPortAudioSetupUtil : public QObject
{
Q_OBJECT
public slots:
void updateBackends();
void updateDevices();
void updateChannels();
public:
ComboBoxModel m_backendModel;
ComboBoxModel m_deviceModel;
PortAudioInitializationGuard()
: m_error(Pa_Initialize())
{
if (m_error != paNoError) { throw std::runtime_error{"PortAudio: could not initialize"}; }
}
~PortAudioInitializationGuard()
{
if (m_error == paNoError) { Pa_Terminate(); }
}
PortAudioInitializationGuard(const PortAudioInitializationGuard&) = delete;
PortAudioInitializationGuard(PortAudioInitializationGuard&&) = delete;
PortAudioInitializationGuard& operator=(const PortAudioInitializationGuard&) = delete;
PortAudioInitializationGuard& operator=(PortAudioInitializationGuard&&) = delete;
private:
PaError m_error = paNoError;
};
#ifdef LMMS_HAVE_PORTAUDIO
namespace gui
{
class ComboBox;
}
} // namespace detail
class AudioPortAudio : public AudioDevice
{
public:
AudioPortAudio( bool & _success_ful, AudioEngine* audioEngine );
AudioPortAudio(bool& successful, AudioEngine* engine);
~AudioPortAudio() override;
inline static QString name()
{
return QT_TRANSLATE_NOOP( "AudioDeviceSetupWidget", "PortAudio" );
}
AudioPortAudio(const AudioPortAudio&) = delete;
AudioPortAudio(AudioPortAudio&&) = delete;
AudioPortAudio& operator=(const AudioPortAudio&) = delete;
AudioPortAudio& operator=(AudioPortAudio&&) = delete;
int process_callback(const float* _inputBuffer, float* _outputBuffer, f_cnt_t _framesPerBuffer);
class setupWidget : public gui::AudioDeviceSetupWidget
{
public:
setupWidget( QWidget * _parent );
~setupWidget() override;
void saveSettings() override;
void show() override;
private:
gui::ComboBox * m_backend;
gui::ComboBox * m_device;
AudioPortAudioSetupUtil m_setupUtil;
} ;
private:
void startProcessing() override;
void stopProcessing() override;
#ifdef PORTAUDIO_V19
static int _process_callback( const void *_inputBuffer, void * _outputBuffer,
unsigned long _framesPerBuffer,
const PaStreamCallbackTimeInfo * _timeInfo,
PaStreamCallbackFlags _statusFlags,
void *arg );
static auto name() -> QString { return QT_TRANSLATE_NOOP("AudioDeviceSetupWidget", "PortAudio"); }
#else
private:
static int processCallback(const void* input, void* output, unsigned long frameCount,
const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void* userData);
#define paContinue 0
#define paComplete 1
#define Pa_GetDeviceCount Pa_CountDevices
#define Pa_GetDefaultInputDevice Pa_GetDefaultInputDeviceID
#define Pa_GetDefaultOutputDevice Pa_GetDefaultOutputDeviceID
#define Pa_IsStreamActive Pa_StreamActive
detail::PortAudioInitializationGuard m_initGuard;
static int _process_callback( void * _inputBuffer, void * _outputBuffer,
unsigned long _framesPerBuffer, PaTimestamp _outTime, void * _arg );
PaStream* m_paStream = nullptr;
std::vector<SampleFrame> m_outBuf;
std::size_t m_outBufPos = 0;
};
} // namespace lmms
namespace lmms::gui {
class AudioPortAudioSetupWidget : public AudioDeviceSetupWidget
{
public:
AudioPortAudioSetupWidget(QWidget* parent);
using PaTime = double;
using PaDeviceIndex = PaDeviceID;
void show() override;
void saveSettings() override;
using PaStreamParameters = struct
{
PaDeviceIndex device;
int channelCount;
PaSampleFormat sampleFormat;
PaTime suggestedLatency;
void *hostApiSpecificStreamInfo;
} PaStreamParameters;
#endif // PORTAUDIO_V19
PaStream * m_paStream;
PaStreamParameters m_outputParameters;
PaStreamParameters m_inputParameters;
bool m_wasPAInitError;
SampleFrame* m_outBuf;
std::size_t m_outBufPos;
fpp_t m_outBufSize;
bool m_stopped;
} ;
private:
class DeviceSelectorWidget;
QComboBox* m_backendComboBox = nullptr;
DeviceSelectorWidget* m_inputDevice = nullptr;
DeviceSelectorWidget* m_outputDevice = nullptr;
};
} // namespace lmms::gui
#endif // LMMS_HAVE_PORTAUDIO
} // namespace lmms
#endif // LMMS_AUDIO_PORTAUDIO_H

View File

@@ -23,6 +23,7 @@
*/
#include "AudioEngine.h"
#include <iostream>
#include "MixHelpers.h"
#include "denormals.h"
@@ -878,7 +879,7 @@ AudioDevice * AudioEngine::tryAudioDevices()
#ifdef LMMS_HAVE_PORTAUDIO
if( dev_name == AudioPortAudio::name() || dev_name == "" )
if (dev_name == AudioPortAudio::name() || dev_name.isEmpty())
{
dev = new AudioPortAudio( success_ful, this );
if( success_ful )

View File

@@ -23,443 +23,314 @@
*
*/
#include <iostream>
#include "AudioPortAudio.h"
#ifndef LMMS_HAVE_PORTAUDIO
namespace lmms
{
void AudioPortAudioSetupUtil::updateBackends()
{
}
void AudioPortAudioSetupUtil::updateDevices()
{
}
void AudioPortAudioSetupUtil::updateChannels()
{
}
} // namespace lmms
#endif
#include "lmmsconfig.h"
#ifdef LMMS_HAVE_PORTAUDIO
#include <QFormLayout>
#include "ConfigManager.h"
#include "ComboBox.h"
#include "AudioEngine.h"
#include "AudioPortAudio.h"
#include "ConfigManager.h"
#include "LcdSpinBox.h"
namespace lmms
namespace {
enum class Direction
{
Input,
Output
};
AudioPortAudio::AudioPortAudio( bool & _success_ful, AudioEngine * _audioEngine ) :
AudioDevice(std::clamp<ch_cnt_t>(
ConfigManager::inst()->value("audioportaudio", "channels").toInt(),
DEFAULT_CHANNELS,
DEFAULT_CHANNELS), _audioEngine),
m_paStream( nullptr ),
m_wasPAInitError( false ),
m_outBuf(new SampleFrame[audioEngine()->framesPerPeriod()]),
m_outBufPos( 0 )
constexpr auto tag()
{
_success_ful = false;
m_outBufSize = audioEngine()->framesPerPeriod();
PaError err = Pa_Initialize();
if( err != paNoError ) {
printf( "Couldn't initialize PortAudio: %s\n", Pa_GetErrorText( err ) );
m_wasPAInitError = true;
return;
}
if( Pa_GetDeviceCount() <= 0 )
{
return;
}
const QString& backend = ConfigManager::inst()->value( "audioportaudio", "backend" );
const QString& device = ConfigManager::inst()->value( "audioportaudio", "device" );
PaDeviceIndex inDevIdx = -1;
PaDeviceIndex outDevIdx = -1;
for( int i = 0; i < Pa_GetDeviceCount(); ++i )
{
const auto di = Pa_GetDeviceInfo(i);
if( di->name == device &&
Pa_GetHostApiInfo( di->hostApi )->name == backend )
{
inDevIdx = i;
outDevIdx = i;
}
}
if( inDevIdx < 0 )
{
inDevIdx = Pa_GetDefaultInputDevice();
}
if( outDevIdx < 0 )
{
outDevIdx = Pa_GetDefaultOutputDevice();
}
if( inDevIdx < 0 || outDevIdx < 0 )
{
return;
}
double inLatency = 0;//(double)audioEngine()->framesPerPeriod() / (double)sampleRate();
double outLatency = 0;//(double)audioEngine()->framesPerPeriod() / (double)sampleRate();
//inLatency = Pa_GetDeviceInfo( inDevIdx )->defaultLowInputLatency;
//outLatency = Pa_GetDeviceInfo( outDevIdx )->defaultLowOutputLatency;
const int samples = audioEngine()->framesPerPeriod();
// Configure output parameters.
m_outputParameters.device = outDevIdx;
m_outputParameters.channelCount = channels();
m_outputParameters.sampleFormat = paFloat32; // 32 bit floating point output
m_outputParameters.suggestedLatency = outLatency;
m_outputParameters.hostApiSpecificStreamInfo = nullptr;
// Configure input parameters.
m_inputParameters.device = inDevIdx;
m_inputParameters.channelCount = DEFAULT_CHANNELS;
m_inputParameters.sampleFormat = paFloat32; // 32 bit floating point input
m_inputParameters.suggestedLatency = inLatency;
m_inputParameters.hostApiSpecificStreamInfo = nullptr;
// Open an audio I/O stream.
err = Pa_OpenStream(
&m_paStream,
supportsCapture() ? &m_inputParameters : nullptr, // The input parameter
&m_outputParameters, // The outputparameter
sampleRate(),
samples,
paNoFlag, // Don't use any flags
_process_callback, // our callback function
this );
if( err == paInvalidDevice && sampleRate() < 48000 )
{
printf("Pa_OpenStream() failed with 44,1 KHz, trying again with 48 KHz\n");
// some backends or drivers do not allow 32 bit floating point data
// with a samplerate of 44100 Hz
setSampleRate( 48000 );
err = Pa_OpenStream(
&m_paStream,
supportsCapture() ? &m_inputParameters : nullptr, // The input parameter
&m_outputParameters, // The outputparameter
sampleRate(),
samples,
paNoFlag, // Don't use any flags
_process_callback, // our callback function
this );
}
if( err != paNoError )
{
printf( "Couldn't open PortAudio: %s\n", Pa_GetErrorText( err ) );
return;
}
printf( "Input device: '%s' backend: '%s'\n", Pa_GetDeviceInfo( inDevIdx )->name, Pa_GetHostApiInfo( Pa_GetDeviceInfo( inDevIdx )->hostApi )->name );
printf( "Output device: '%s' backend: '%s'\n", Pa_GetDeviceInfo( outDevIdx )->name, Pa_GetHostApiInfo( Pa_GetDeviceInfo( outDevIdx )->hostApi )->name );
// TODO: debug AudioEngine::pushInputFrames()
//m_supportsCapture = true;
_success_ful = true;
return "audioportaudio";
}
constexpr auto backendAttribute()
{
return "backend";
}
constexpr auto deviceNameAttribute(Direction direction)
{
switch (direction)
{
case Direction::Input:
return "inputdevice";
case Direction::Output:
return "outputdevice";
}
return "";
}
constexpr auto channelsAttribute(Direction direction)
{
switch (direction)
{
case Direction::Input:
return "inputchannels";
case Direction::Output:
return "outputchannels";
}
return "";
}
int maxChannels(const PaDeviceInfo* info, Direction direction)
{
switch (direction)
{
case Direction::Input:
return info->maxInputChannels;
case Direction::Output:
return info->maxOutputChannels;
}
return 0;
}
} // namespace
namespace lmms {
AudioPortAudio::AudioPortAudio(bool& successful, AudioEngine* engine)
: AudioDevice(DEFAULT_CHANNELS, engine)
, m_outBuf(engine->framesPerPeriod())
{
const auto numDevices = Pa_GetDeviceCount();
if (numDevices < 0)
{
std::cerr << "Pa_GetDeviceCount() failed: " << Pa_GetErrorText(numDevices) << '\n';
successful = false;
return;
}
const auto backend = ConfigManager::inst()->value(tag(), backendAttribute());
const auto inputDeviceName = ConfigManager::inst()->value(tag(), deviceNameAttribute(Direction::Input));
const auto inputDeviceChannels = ConfigManager::inst()->value(tag(), channelsAttribute(Direction::Input)).toInt();
const auto outputDeviceName = ConfigManager::inst()->value(tag(), deviceNameAttribute(Direction::Output));
const auto outputDeviceChannels = ConfigManager::inst()->value(tag(), channelsAttribute(Direction::Output)).toInt();
auto inputDeviceIndex = paNoDevice;
auto outputDeviceIndex = paNoDevice;
for (auto i = 0; i < numDevices && (inputDeviceIndex == paNoDevice || outputDeviceIndex == paNoDevice); ++i)
{
const auto deviceInfo = Pa_GetDeviceInfo(i);
const auto hostApiInfo = Pa_GetHostApiInfo(deviceInfo->hostApi);
if (deviceInfo->name == inputDeviceName && hostApiInfo->name == backend) { inputDeviceIndex = i; }
if (deviceInfo->name == outputDeviceName && hostApiInfo->name == backend) { outputDeviceIndex = i; }
}
const auto sampleRate = engine->baseSampleRate();
const auto framesPerBuffer = engine->framesPerPeriod();
const auto inputLatency
= inputDeviceIndex == paNoDevice ? 0. : Pa_GetDeviceInfo(inputDeviceIndex)->defaultLowInputLatency;
const auto outputLatency
= outputDeviceIndex == paNoDevice ? 0. : Pa_GetDeviceInfo(outputDeviceIndex)->defaultLowOutputLatency;
const auto inputParameters = PaStreamParameters {
.device = inputDeviceIndex,
.channelCount = inputDeviceChannels,
.sampleFormat = paFloat32,
.suggestedLatency = inputLatency,
.hostApiSpecificStreamInfo = nullptr
};
const auto outputParameters = PaStreamParameters {
.device = outputDeviceIndex,
.channelCount = outputDeviceChannels,
.sampleFormat = paFloat32,
.suggestedLatency = outputLatency,
.hostApiSpecificStreamInfo = nullptr
};
const auto inputParametersPtr = inputDeviceIndex == paNoDevice ? nullptr : &inputParameters;
const auto outputParametersPtr = outputDeviceIndex == paNoDevice ? nullptr : &outputParameters;
auto err = Pa_IsFormatSupported(inputParametersPtr, outputParametersPtr, sampleRate);
if (err != paFormatIsSupported)
{
std::cerr << "Pa_IsFormatSupported() failed: " << Pa_GetErrorText(err) << '\n';
successful = false;
return;
}
err = Pa_OpenStream(&m_paStream, inputParametersPtr, outputParametersPtr, sampleRate, framesPerBuffer, paNoFlag,
&AudioPortAudio::processCallback, this);
if (err != paNoError)
{
std::cerr << "Pa_OpenStream() failed: " << Pa_GetErrorText(err) << '\n';
successful = false;
return;
}
successful = true;
setSampleRate(sampleRate);
setChannels(outputDeviceChannels);
}
AudioPortAudio::~AudioPortAudio()
{
stopProcessing();
if( !m_wasPAInitError )
{
Pa_Terminate();
}
delete[] m_outBuf;
Pa_CloseStream(m_paStream);
}
void AudioPortAudio::startProcessing()
{
m_stopped = false;
PaError err = Pa_StartStream( m_paStream );
if( err != paNoError )
{
m_stopped = true;
printf( "PortAudio error: %s\n", Pa_GetErrorText( err ) );
}
Pa_StartStream(m_paStream);
}
void AudioPortAudio::stopProcessing()
{
if( m_paStream && Pa_IsStreamActive( m_paStream ) )
{
m_stopped = true;
PaError err = Pa_StopStream( m_paStream );
if( err != paNoError )
{
printf( "PortAudio error: %s\n", Pa_GetErrorText( err ) );
}
}
Pa_StopStream(m_paStream);
}
int AudioPortAudio::process_callback(const float* _inputBuffer, float* _outputBuffer, f_cnt_t _framesPerBuffer)
int AudioPortAudio::processCallback(const void*, void* output, unsigned long frameCount,
const PaStreamCallbackTimeInfo*, PaStreamCallbackFlags, void* userData)
{
if( supportsCapture() )
{
audioEngine()->pushInputFrames( (SampleFrame*)_inputBuffer, _framesPerBuffer );
}
const auto device = static_cast<AudioPortAudio*>(userData);
if( m_stopped )
const auto outputBuffer = static_cast<float*>(output);
for (auto frame = std::size_t{0}; frame < frameCount; ++frame)
{
memset( _outputBuffer, 0, _framesPerBuffer *
channels() * sizeof(float) );
return paComplete;
}
while( _framesPerBuffer )
{
if( m_outBufPos == 0 )
if (device->m_outBufPos == 0 && device->getNextBuffer(device->m_outBuf.data()) == 0)
{
// frames depend on the sample rate
const fpp_t frames = getNextBuffer( m_outBuf );
if( !frames )
{
m_stopped = true;
memset( _outputBuffer, 0, _framesPerBuffer *
channels() * sizeof(float) );
return paComplete;
}
m_outBufSize = frames;
}
const auto min_len = std::min(_framesPerBuffer, m_outBufSize - m_outBufPos);
for( fpp_t frame = 0; frame < min_len; ++frame )
{
for( ch_cnt_t chnl = 0; chnl < channels(); ++chnl )
{
(_outputBuffer + frame * channels())[chnl] = AudioEngine::clip(m_outBuf[frame][chnl]);
}
std::fill(outputBuffer + frame * device->channels(), outputBuffer + frameCount * device->channels(), 0.f);
return paComplete;
}
_outputBuffer += min_len * channels();
_framesPerBuffer -= min_len;
m_outBufPos += min_len;
m_outBufPos %= m_outBufSize;
if (device->channels() == 1)
{
outputBuffer[frame] = device->m_outBuf[device->m_outBufPos].average();
}
else
{
outputBuffer[frame * device->channels()] = device->m_outBuf[device->m_outBufPos][0];
outputBuffer[frame * device->channels() + 1] = device->m_outBuf[device->m_outBufPos][1];
}
device->m_outBufPos = (device->m_outBufPos + 1) % device->m_outBuf.size();
}
return paContinue;
}
} // namespace lmms
namespace lmms::gui {
int AudioPortAudio::_process_callback(
const void *_inputBuffer,
void * _outputBuffer,
unsigned long _framesPerBuffer,
const PaStreamCallbackTimeInfo * _timeInfo,
PaStreamCallbackFlags _statusFlags,
void * _arg )
class AudioPortAudioSetupWidget::DeviceSelectorWidget : public QGroupBox
{
Q_UNUSED(_timeInfo);
Q_UNUSED(_statusFlags);
auto _this = static_cast<AudioPortAudio*>(_arg);
return _this->process_callback( (const float*)_inputBuffer,
(float*)_outputBuffer, _framesPerBuffer );
}
void AudioPortAudioSetupUtil::updateBackends()
{
PaError err = Pa_Initialize();
if( err != paNoError ) {
printf( "Couldn't initialize PortAudio: %s\n", Pa_GetErrorText( err ) );
return;
}
for( int i = 0; i < Pa_GetHostApiCount(); ++i )
public:
DeviceSelectorWidget(const QString& deviceLabel, Direction direction, QWidget* parent = nullptr)
: QGroupBox{parent}
, m_deviceComboBox{new QComboBox{this}}
, m_channelSpinBox{new LcdSpinBox{1, this}}
, m_direction(direction)
{
const auto hi = Pa_GetHostApiInfo(i);
m_backendModel.addItem( hi->name );
m_channelSpinBox->setModel(&m_channelModel);
const auto layout = new QFormLayout{this};
layout->addRow(deviceLabel, m_deviceComboBox);
layout->addRow(tr("Channels"), m_channelSpinBox);
connect(m_deviceComboBox, qOverload<int>(&QComboBox::currentIndexChanged), this,
[this](int index) { refreshChannels(m_deviceComboBox->itemData(index).toInt()); });
}
Pa_Terminate();
}
void AudioPortAudioSetupUtil::updateDevices()
{
PaError err = Pa_Initialize();
if( err != paNoError ) {
printf( "Couldn't initialize PortAudio: %s\n", Pa_GetErrorText( err ) );
return;
}
// get active backend
const QString& backend = m_backendModel.currentText();
int hostApi = 0;
for( int i = 0; i < Pa_GetHostApiCount(); ++i )
void refreshFromConfig(PaHostApiIndex backendIndex)
{
const auto hi = Pa_GetHostApiInfo(i);
if( backend == hi->name )
using namespace lmms;
m_deviceComboBox->clear();
for (auto i = 0, deviceCount = Pa_GetDeviceCount(); i < deviceCount; ++i)
{
hostApi = i;
break;
const auto deviceInfo = Pa_GetDeviceInfo(i);
if (maxChannels(deviceInfo, m_direction) > 0 && deviceInfo->hostApi == backendIndex)
{
m_deviceComboBox->addItem(deviceInfo->name, i);
}
}
const auto selectedDeviceName = ConfigManager::inst()->value(tag(), deviceNameAttribute(m_direction));
const auto selectedDeviceIndex = std::max(0, m_deviceComboBox->findText(selectedDeviceName));
m_deviceComboBox->setCurrentIndex(selectedDeviceIndex);
}
// get devices for selected backend
m_deviceModel.clear();
for( int i = 0; i < Pa_GetDeviceCount(); ++i )
void refreshChannels(PaDeviceIndex deviceIndex)
{
const auto di = Pa_GetDeviceInfo(i);
if( di->hostApi == hostApi )
const auto maxChannelCount = maxChannels(Pa_GetDeviceInfo(deviceIndex), m_direction);
const auto channelCount = ConfigManager::inst()->value(tag(), channelsAttribute(m_direction)).toInt();
m_channelModel.setRange(1, maxChannelCount);
m_channelModel.setValue(channelCount == 0 ? DEFAULT_CHANNELS : channelCount);
m_channelSpinBox->setNumDigits(QString::number(maxChannelCount).length());
}
void saveToConfig()
{
ConfigManager::inst()->setValue(tag(), deviceNameAttribute(m_direction), m_deviceComboBox->currentText());
ConfigManager::inst()->setValue(tag(), channelsAttribute(m_direction), QString::number(m_channelModel.value()));
}
private:
QComboBox* m_deviceComboBox = nullptr;
LcdSpinBox* m_channelSpinBox = nullptr;
IntModel m_channelModel;
Direction m_direction;
};
AudioPortAudioSetupWidget::AudioPortAudioSetupWidget(QWidget* parent)
: AudioDeviceSetupWidget{AudioPortAudio::name(), parent}
, m_backendComboBox{new QComboBox{this}}
, m_inputDevice{new DeviceSelectorWidget{tr("Input device"), Direction::Input}}
, m_outputDevice(new DeviceSelectorWidget{tr("Output device"), Direction::Output})
{
constexpr auto formVerticalSpacing = 10;
const auto form = new QFormLayout{this};
form->setRowWrapPolicy(QFormLayout::WrapLongRows);
form->setVerticalSpacing(formVerticalSpacing);
form->addRow(tr("Backend"), m_backendComboBox);
form->addRow(m_outputDevice);
form->addRow(m_inputDevice);
connect(m_backendComboBox, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int index) {
m_inputDevice->refreshFromConfig(m_backendComboBox->itemData(index).toInt());
m_outputDevice->refreshFromConfig(m_backendComboBox->itemData(index).toInt());
});
}
void AudioPortAudioSetupWidget::show()
{
static auto s_initGuard = detail::PortAudioInitializationGuard{};
if (m_backendComboBox->count() == 0)
{
for (auto i = 0, backendCount = Pa_GetHostApiCount(); i < backendCount; ++i)
{
m_deviceModel.addItem( di->name );
m_backendComboBox->addItem(Pa_GetHostApiInfo(i)->name, i);
}
}
Pa_Terminate();
}
void AudioPortAudioSetupUtil::updateChannels()
{
PaError err = Pa_Initialize();
if( err != paNoError ) {
printf( "Couldn't initialize PortAudio: %s\n", Pa_GetErrorText( err ) );
return;
}
// get active backend
Pa_Terminate();
}
AudioPortAudio::setupWidget::setupWidget( QWidget * _parent ) :
AudioDeviceSetupWidget( AudioPortAudio::name(), _parent )
{
using gui::ComboBox;
QFormLayout * form = new QFormLayout(this);
m_backend = new ComboBox( this, "BACKEND" );
form->addRow(tr("Backend"), m_backend);
m_device = new ComboBox( this, "DEVICE" );
form->addRow(tr("Device"), m_device);
/* LcdSpinBoxModel * m = new LcdSpinBoxModel( );
m->setRange( DEFAULT_CHANNELS, DEFAULT_CHANNELS );
m->setStep( 2 );
m->setValue( ConfigManager::inst()->value( "audioportaudio",
"channels" ).toInt() );
m_channels = new LcdSpinBox( 1, this );
m_channels->setModel( m );
m_channels->setLabel( tr( "Channels" ) );
m_channels->move( 308, 20 );*/
connect( &m_setupUtil.m_backendModel, SIGNAL(dataChanged()),
&m_setupUtil, SLOT(updateDevices()));
connect( &m_setupUtil.m_deviceModel, SIGNAL(dataChanged()),
&m_setupUtil, SLOT(updateChannels()));
m_backend->setModel( &m_setupUtil.m_backendModel );
m_device->setModel( &m_setupUtil.m_deviceModel );
}
AudioPortAudio::setupWidget::~setupWidget()
{
disconnect( &m_setupUtil.m_backendModel, SIGNAL(dataChanged()),
&m_setupUtil, SLOT(updateDevices()));
disconnect( &m_setupUtil.m_deviceModel, SIGNAL(dataChanged()),
&m_setupUtil, SLOT(updateChannels()));
}
void AudioPortAudio::setupWidget::saveSettings()
{
ConfigManager::inst()->setValue( "audioportaudio", "backend",
m_setupUtil.m_backendModel.currentText() );
ConfigManager::inst()->setValue( "audioportaudio", "device",
m_setupUtil.m_deviceModel.currentText() );
/* ConfigManager::inst()->setValue( "audioportaudio", "channels",
QString::number( m_channels->value<int>() ) );*/
}
void AudioPortAudio::setupWidget::show()
{
if( m_setupUtil.m_backendModel.size() == 0 )
{
// populate the backend model the first time we are shown
m_setupUtil.updateBackends();
const QString& backend = ConfigManager::inst()->value(
"audioportaudio", "backend" );
const QString& device = ConfigManager::inst()->value(
"audioportaudio", "device" );
int i = std::max(0, m_setupUtil.m_backendModel.findText(backend));
m_setupUtil.m_backendModel.setValue( i );
m_setupUtil.updateDevices();
i = std::max(0, m_setupUtil.m_deviceModel.findText(device));
m_setupUtil.m_deviceModel.setValue( i );
const auto selectedBackendName = ConfigManager::inst()->value(tag(), backendAttribute());
const auto selectedBackendIndex = std::max(0, m_backendComboBox->findText(selectedBackendName));
m_backendComboBox->setCurrentIndex(selectedBackendIndex);
}
AudioDeviceSetupWidget::show();
}
} // namespace lmms
void AudioPortAudioSetupWidget::saveSettings()
{
ConfigManager::inst()->setValue(tag(), backendAttribute(), m_backendComboBox->currentText());
m_inputDevice->saveToConfig();
m_outputDevice->saveToConfig();
}
} // namespace lmms::gui
#endif // LMMS_HAVE_PORTAUDIO

View File

@@ -484,6 +484,9 @@ void MainWindow::finalize()
else if( Engine::audioEngine()->audioDevStartFailed() || !AudioEngine::isAudioDevNameValid(
ConfigManager::inst()->value( "audioengine", "audiodev" ) ) )
{
QMessageBox::critical(nullptr, "Audio device setup failed",
tr("Failed to setup audio device for playback. Try adjusting your audio device settings (e.g. the sample rate), then restart LMMS."));
// if so, offer the audio settings section of the setup dialog
SetupDialog sd( SetupDialog::ConfigTab::AudioSettings );
sd.exec();

View File

@@ -510,7 +510,7 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) :
#ifdef LMMS_HAVE_PORTAUDIO
m_audioIfaceSetupWidgets[AudioPortAudio::name()] =
new AudioPortAudio::setupWidget(as_w);
new AudioPortAudioSetupWidget(as_w);
#endif
#ifdef LMMS_HAVE_SOUNDIO